// http://testsuites.opera.com/JSON/performance/001.html
// http://krakenbenchmark.mozilla.org/
// http://cs.au.dk/~amoeller/papers/jsrefactor/
// http://wingolog.org/archives/2011/08/02/a-closer-look-at-crankshaft-v8s-optimizing-compiler
// http://www.mail-archive.com/es-discuss@mozilla.org/msg09590.html

// funfuzz http://www.squarefree.com/2007/08/02/introducing-jsfunfuzz/ http://www.squarefree.com/categories/fuzzing/

//what you actually should do is that whenever an assignment is found, the type tracking should return 
//the expression and the type of the expression extracted at a later point. this would complicate limiting
//operators though, specifically the + and +=. not sure but i'll have to somehow resolve that later.

// tofix: legacy typing may be thrown out...
// tofix: flow stuff for break with label...
// tofix: properly handle +=, rather than chicken out

// H certain operators are static too (void, typeof on primitives), take into account with static expressions
// M if some token is an array and .push or .unshift is called on on it, maybe add the type to the array types too...
// M rewriter tool should accept granularity
// M with
// M highlight all search hits
// M variable grouping tool should crop trailing whitespace generated by the tool, and it should re-connect single variable declarations if immediately followed by an initializer
// L top padding issue so that it behaves the same regardless of where zeon is used. maybe use caret popup to match alignment
// L fix cli integration in node and whatever
// L this should be an error... for (x=5 in o) ... is illegal; lhs assignments in for-in must be warpped in parens
// L branch coverage tool ( http://ged.msu.edu/courses/2009-fall-cse-491/cse491-2009-lab10.html )
// L code coverage tool
// L remove blocks under a certain condition (if (debug) ...)
// L remove single-statement non-required blocks: if (foo) { bar; }
// L static dead code (like if(false) ...)
// L alternative jsdoc syntax
// L evals ( http://kangax.github.com/jstests/indirect-eval-testsuite/ )
// L folding
// L warn for dangerous asi's (simple patterns)
// L remember values (if var x = y; and x is determined to have certain properties later, y should probably get them as well) --> value tracking
// L concrete typing; using tokens for types until they have been resolved, solving the lookahead problem
// L strict mode: http://www.java-script.limewebs.com/strictMode/test_hosted.html
// L http://www.rkcole.com/articles/other/CodeMetrics-CCN.html
// L aggressive dead code checks; is a function called?
// L investigate undo stack stuff... i think its a mine-field, but whatever

// jsdoc:
// http://code.google.com/intl/nl/closure/compiler/docs/js-for-compiler.html#types
// http://code.google.com/p/jsdoc-toolkit/
// http://code.google.com/p/jsdoc-toolkit/wiki/FAQ

// extension fun
/*
http://blog.mozilla.com/addons/2011/06/21/add-on_sdk-builder-_beta/
https://builder.addons.mozilla.org/
http://twitter.com/paul_irish/statuses/85092057659604992
http://code.google.com/chrome/extensions/experimental.webInspector.panels.html
http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/tabs/inspector/tabs_api.html?content-type=text/plain
*/






var Zeon = function(input, config){
	this.scopes = [];
	this.lastInput = input;

	this.config = config || {};
};

Zeon.getNewConfig = function(){
	return {
		// general
		'zeon visual output': true,

		// visual
		'warnings': false,
		'markers': false,
		'scope depth': true,
		'warn if scope depth exceeds': 2,
		'ruler': true,
		'type annotations': true,
		'trailing whitespace cue': true,

		// tooling
		'caret popup': true,
		'dim undefined ifdefs': true,
		'minify variable names too': true,
		'minify property names too': true,
		'minify property names always': false,
		'minify uses newlines for semis': true,
		'hoisting fix moves func decl to top': true,
		'load saved code at start': false,

		// ==== warning toggles ====
		'missing block good': true,
		'missing block bad': true,
		'assignment in header': true,
		'weak comparison': true,
		'dangling underscore': true,
		'dot and not can be confusing': true,
		'inc dec operator': true,
		'comma in group makes inc/dec fail': true,
		'binary operator': true,
		'use dot access': true,
		'continue only in loops': true,
		'break needs loop or label': true,
		'return only in function': true,
		'trailing decimal': true,
		'leading decimal': true,
		'regex confusion': true,
		'number dot': true,
		'assignment bad': true,
		'assignment this': true,
		'bad string escapement': true,
		'unlikely regex escapement': true,
		'avoid hex': true,
		'caller callee': true,
		'octal escape': true,
		'00': true,
		'regexp call': true,
		'confusing plusses': true,
		'confusing minusses': true,
		'double bang': true,
		'control char': true,
		'unsafe char': true,
		'invalid unicode escape in string': true,
		'invalid unicode escape in regex': true,
		'invalid hex escape in string': true,
		'invalid hex escape in regex': true,
		'catch var assignment': true,
		'bad constructor': true,
		'array constructor': true,
		'error constructor': true,
		'very bad constructor': true,
		'Function is eval': true,
		'function wrapped': true,
		'document.write': true,
		'iteration function': true,
		'empty block': true,
		'eval': true,
		'empty regex char class': true,
		'extra comma': true,
		'double new': true,
		'double delete': true,
		'undefined': true,
		'duplicate objlit prop': true,
		'timer eval': true,
		'group vars': true,
		'func decl at top': true,
		'is label': true,
		'math call': true,
		'new wants parens': true,
		'missing radix': true,
		'nested comment': true,
		'new statement': true,
		'dont use __proto__': true,
		'empty switch': true,
		'quasi empty switch': true,
		'empty clause': true,
		'clause should break': true,
		'switch is an if': true,
		'unwrapped for-in': true,
		'in out of for': true,
		'use {}': true,
		'use []': true,
		'double block': true,
		'useless block': true,
		'use capital namespacing': true,
		'constructor called as function': true,
		'cannot inc/dec on call expression': true,
		'inc/dec only valid on vars': true,
		'bad asi pattern': true,
		'unlikely typeof result': true,
		'weird typeof op': true,
		'typeof always string': true,
		'static expression': true,
		'static condition': true,
		'pragma requires name parameter': true,
		'pragma requires value parameter': true,
		'missing ifdef': true,
		'missing inline': true,
		'pragma start missing end': true,
		'macro name should be identifier': true,
		'is dev relic': true,
		'multiple operators on same level': true,
		'useless multiple throw args': true,
		'unnecessary parentheses': true,
		'uninitialized value in loop': true,
		'jsdoc type mismatch': true,
		'prop not declared on proto': true,
		'trailing comma': true,
		'ASI': true,
		'empty statement': true,
		'premature usage': true,
		'unused': true,
		'dead code': true,
		'useless parens': true,
		'known implicit global': true,
		'unknown implicit global': true,
		'duplicate label': true,
		'label not found': true,
		'silly delete construct': true,
		'delete not a function': true,
		'weird delete operand': true,
		'cannot call/apply that': true,
		'func expr name is read-only': true
	};
};

Zeon.getUniqueItems = function(arr){
	var newarr = [];
	if (!arr) return [];
	for (var i=0; i<arr.length; ++i) {
		if (newarr.indexOf(arr[i]) < 0) newarr.push(arr[i]);
	}
	return newarr;
};
Zeon.uniqueInline = function(arr){
	// filter arr and make sure it only has unique occurrences
	for (var i=0; i<arr.length-1; ++i) {
		var j = arr.length;
		while (--j > i) {
			if (arr[i] == arr[j]) {
				arr.splice(j, 1);
			}
		}
	}
	// arr should now only contain unique var names
};
Zeon.uniqueNamesByValue = function(arr){
	var newarr = [];
	for (var i=0; i<arr.length; ++i) {
		var found = false;
		for (var j=0; j<newarr.length; ++j) {
			if (newarr[j].value == arr[i].value) {
				found = true;
				break;
			}
		}
		if (!found) newarr.push(arr[i]);
	}
	return newarr;
};


Zeon.prototype = {
	config: null,

	// source
	lastInput: '',
	tree: null,

	tokenizer: null,
	parser: null,
	hasError:null,

	lastJsdoc:null,

	root: null,
	scopes:null,
	globalScope: null,

	collects: null,
	pragmas: null,

	has: {}.hasOwnProperty,
	hasOwn: function(obj, key){ return this.has.call(obj, key); }, //#macro this.hasOwn this.has.call

	regexEcma: /^Object$|^Array$|^String$|^Number$|^Boolean$|^Date$|^Function$|^RegExp$|^Error$|^arguments$|^Math$|^JSON$|^parseInt$|^parseFloat$|^isFinite$|^isNaN$|^undefined$|^eval$|^true$|^false$|^null$/,
	regexBrowser: /^document$|^window$|^setTimeout$|^setInterval$|^clearInterval$|^clearTimeout$|^console$|^navigator$|^Image$|^alert$|^confirm$|^XMLHttpRequest$/,
	regexDevSigns: /^console$|^log$|^debug$|^debugger$|^alert$|^foo$|^bar$|^baz$|^boo$|^tmp$|^temp$|^test$/,
	regexBuiltinObjects: /^Object$|^Array$|^String$|^Number$|^Boolean$|^Date$|^Function$|^RegExp$|^Error$|^Math$|^JSON$/,
	regexBuiltinBadConstructors: /^Object$|^Array$|^String$|^Number$|^Boolean$|^Function$|^RegExp$|^Error$|^Math$|^JSON$/,
	regexBinaryOps: /^\&$|^\|$|^\^$|^\~$|^<<$|^>>$|^>>>$/,
	regexControlChars: /[\u0000-\u001F]/,
	regexStringEscapement: /(?:^|[^\\])\\[^'"\\bxfnrtvu]/, // first group is to make sure prev char is not a backslash
	regexRegexEscapement: /(?:^|[^\\])\\[^$\\\/.*+?()[\]{}|\^fnrtvdDsSwWu\-]/, // first group is to make sure prev char is not a backslash
	regexBoolOps: /^<=$|^>=$|^<$|^>$|^==$|^!=$|^===$|^\!==$|^in$|^instanceof$/,
	regexNumberOps: /^\*=?$|^\/=?$|^%=?$|^-=?$|^<<=?$|^>>>=?$|^\&=?$|^\|=?$|^\^=?$/,
	regexInvalidUnicodeEscape: /\\u[0-9A-Fa-f]{0,3}[^0-9A-Fa-f]/,
	regexInvalidHexEscape: /\\x[0-9A-Fa-f]?[^0-9A-Fa-f]/,
	regexToDoFix: /(todo)|(tofix)|(fixme)|(to-do)/i,
	regexPragmas: /^\/\/#((?:define)|(?:(?:else)?ifdef)|(?:elsedef)|(?:endif)|(?:macro)|(?:f?inline)|(?:f?endline))(?:\s+(\S+)(?:\s+(.+))?)?/i, // optionally also match the args
	regexValidMacro: /^[a-zA-Z\$_](?:\.?[a-zA-Z0-9\$_])+$/, // macros may contain dots but must otherwise be valid identifiers / property access

	// http://tech.groups.yahoo.com/group/jslint_com/message/22
	regexUnsafeCharacters: /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,

	// [*] @(name)[ ((param)[ (param)[ (rest)]]])
	// result: [match, name, rest-after-name, param1, param2, rest-after-params]
	regexJsDoc: /^(\s*\/?\**\s*)(\@)([a-zA-Z0-9]+)(?:(\s+)(([^\s]+)(?:(\s+)([^\s]+)(?:(\s+)(.+))?)?))?$/,

	regexJsDocScrub: /^(.*)?\s*\*+\/\s*$/, // removes trailing */ if it exists

	// https://developer.mozilla.org/en/JavaScript/Reference/Operators/Operator_Precedence
	// this only contains binary operators.
	// only ternary (?:) and assignments are right associative: x=y=z => x=(y=z). ternary is done implicitly by the parser
	// and assignments explicitly :) so all others are left associative (when precedence is equal, evaluate left to right)
	precedence: {
		'*':1, '/':1, '%':1,
		'+':2, '-':2,
		'<<':3, '>>':3, '>>>':3,
		'<':4, '<=':4, '>':4, '>=':4, 'in':4, 'instanceof':4,
		'==':5, '!=':5, '===':5, '!==':5,
		'&':6,
		'^':7,
		'|':8,
		'&&':9,
		'||':10,
		'?':11, ':':11, // always put ? left of :
		'=':12, '<<=':12, '>>=':12, '>>>=':12, '+=':12, '-=':12, '*=':12, '/=':12, '%=':12, '&=':12, '^=':12, '|=':12,
		',':13
	},

	// initialized during process
	_currentLine: null, // temp during process, refs the top of the lines array
	lines: null, // an array of arrays, for each line contains the tokens in that line. each line array has a start and stop property, referring to the input length
	wtree: null, // all tokens of the parse tree in a single linear array
	btree: null, // also all tokens in single array, but without "whitespace" (WhiteSpace, LineTerminators, Comments)

	// order flow statements from weak to strong.
	IS_CONTINUE: 1,      //#macro this.IS_CONTINUE 1
	IS_BREAK: 2,         //#macro this.IS_BREAK 2
	IS_LABELED_BREAK: 3, //#macro this.IS_LABELED_BREAK 3
	IS_RETURN: 4,        //#macro this.IS_RETURN 4
	IS_THROW: 5,         //#macro this.IS_THROW 5

	// parsing phase (directive, var, func, rest)
	PHASE_DIRECTIVE: 0, //#macro this.PHASE_DIRECTIVE 0
	PHASE_VAR: 1,       //#macro this.PHASE_VAR 1
	PHASE_FUNC: 2,      //#macro this.PHASE_FUNC 2
	PHASE_REST: 3,      //#macro this.PHASE_REST 3

	getProperFunctionName: function(token){
		// function foo(){}
		if (token.isFuncDeclKeyword) {
			// there is just one keyword responsible for this function. use it.
			var name = token.funcName;
		} else if (token.isFuncExprKeyword) {
			// look for these patterns:
			// var foo = function(){}
			// foo = function(){}
			// obj.foo = function(){}
			// (foo) = fu...
			// (obj.foo) = fu...
			// {foo: function(){} }, foo may not be a label (would be bad anyways)
			// {"foo": function(){} } 
			// {599: function(){} }
			// so the previous token should be a colon or an equal sign. else we stop
			// (ps: a function expression must be preceeded by at least some token, else it would be a decl)
			var btree = this.btree;
			var prev = btree[token.tokposb-1];
			if (prev.value == '=' || prev.value == ':') {
				var name = btree[token.tokposb-2];
			} else if (prev.value != '(' && prev.value != '+' && prev.value != 'new' && prev.value != ',' && prev.value != '?' && prev.value != ':' && prev.value != '[' && prev.value == 'in' && !prev.forEachHeaderStop) {
				if (!this.hasError) console.error("getProperFunctionName: Expected a colon or assignment before, was", 'prev', prev.value, prev, 'token', token.value, token, 'same?',token==prev, [this.lastInput]);
			}
		} else {
			if (!this.hasError) console.warn(['unknown function type. expected func or expr, found neither.', token,[this.lastInput]]);
		}
		return name;
	},
	getJspath: function(match, absolute){
		// for jspath:
		// object properties get dot
		// scopes are denoted by /
		// global scope causes all paths to start with /
		// prototype methods get # (for any path, simply replace ".prototype." with a hash (#))
		// catch variable get a ! prefix: foo!e (catch var is usually the end of the line anyways)

		// all functions can also be addressed with (n), where n is the nth function defined like that in the current scope
		// likewise, you can address objects like {n}
		// likewise, you can address arrays like [n]
		// the (n), {n} and [n] will safely give you an absolute path, where naming might not (assignment of two object literals to the same var)

		// if there are multiple occurrences and you want a specific one, they are indexed like arrays: foo>str[2] is the third mention of str in the function foo
		// if there's a dynamic property that's static but illegal identifier, just quote it after the dot: obj."illegal stuff">foo

		// obj.foo = function(){ function Person(){} Person.prototype.print = function(){ out = ""; alert('jspath', out.length); }; };
		// jspath for out.length would be:
		// obj.foo>Person#print>out.length

		if ((absolute && !match.jspathAbsolute) || (!absolute && !match.jspathRelative)) {
			var returnValue = '[unknown]';
			if (match.global) returnValue = '/';
			else if (match.scopeFor) {
				returnValue = this.getJspath(match.scopeFor, absolute)+'/';
			} else if (match.isObjectLiteralStart) {
				var name = this.getProperObjectName(match);
				if (name && !absolute) returnValue = this.getJspath(name, absolute);
				else returnValue = this.getJspath(match.targetScope, absolute)+'{'+match.objectId+'}';
			} else if (match.isArrayLiteralStart) {
				returnValue = this.getJspath(match.targetScope, absolute)+'['+match.arrayId+']';
			} else if (match.isPropertyOf) {
				// forgot why i'm checking explicitly for an object literal start... :)
				//if (match.isPropertyOf.isObjectLiteralStart) returnValue = this.getJspath(match.isPropertyOf)+'.'+match.value;
				//else 
				returnValue = this.getJspath(match.isPropertyOf, absolute)+'.'+match.value;
			} else if (match.functionId >= 0) {
				var name = this.getProperFunctionName(match);
				if (name && !absolute) returnValue = this.getJspath(name, absolute);
				else returnValue = this.getJspath(match.scope[0], absolute)+'('+match.functionId+')';
			} else if (match.value) {
				if (match.catchId >= 0) returnValue = this.getJspath(match.targetScope, absolute)+'!'+match.catchId;
				else if (!match.targetScope) {
//					console.log('No scope ref found (happens for object literal properties and property names with dynamic access before it)');
//					console.log("for error", match); 
//					throw "every ref should have a scope ref...";
				} else if (!match.targetScope.isGlobal) {
					returnValue = this.getJspath(match.targetScope, absolute)+match.value;
				} else {
					returnValue = '/'+match.value;
				}
			} else {
				if (!this.hasError) console.log(["getJspath problem", match, this.lastInput]);
			}

			if (absolute) match.jspathAbsolute = returnValue.replace(/\.prototype\./g, '#');
			else match.jspathRelative = returnValue.replace(/\.prototype\./g, '#');
		}

		if (absolute) return match.jspathAbsolute;
		return match.jspathRelative;
	},
	getProperObjectName: function(token){
		// look for these patterns:
		// var foo = {}
		// obj.foo = {}
		// {foo: {} }, foo may not be a label (would be bad anyways)
		// so the previous token should be a colon or an equal sign. else we stop
		// (ps: a objects must be preceeded by at least some token, else it would be a block)
		var nwtree = this.btree;
		var prev = nwtree[token.tokposb-1];
		if (prev.value == '=' || prev.value == ':') {
			var name = nwtree[token.tokposb-2];
		} else if (prev.value != '(' && prev.value != ',') {
			if (!this.hasError) console.log("getProperObjectName: Expected a colon or assignment before, was", 'prev', prev.value, prev, 'token', token.value, token, 'same?',token==prev,[this.lastInput]);
		}
		return name;
	},

	//#ifdef DEV_MODE (not meant for release)
	/**
	 * Quickly create a tokenstream from the (raw) parse tree.
	 * The function walks the parse tree and puts all non-whitespace
	 * tokens in an array and returns that array.
	 * You can use this to get the two arrays without running the other zeon stuff
	 * @recursive
	 * @param {Array} stack
	 * @param {string|undefined} input when supplied, all tokens that dont have a value property will get it (contains the actual string they span)
	 * @param {Array|undefined} noWhite will contain actual "token stream", so without the whitespace tokens (that also excludes line terminators and comments)
	 * @param {Array|undefined} all will contain any token of the input source
	 * @return {Array|undefined} noWhite||all
	 */
	toTokenStream: function(stack, input, noWhite, all){
		for (var i=0; i<stack.length; ++i) {
			var token = stack[i];
			if (token instanceof Array) {
				this.toTokenStream(token, input, noWhite, all);
			} else {
				// set .value if not already (and input source was given)
				if (input && !token.value) {
					token.value = input.substring(token.start, token.stop);
				}
				// create list with all tokens, if all array is supplied
				if (all) {
					all.push(token);
				}
				// create actual token stream, if noWhite array is supplied
				if (noWhite && token.name != 7/*COMMENT_SINGLE*/ && token.name != 9/*WHITE_SPACE*/ && token.name != 8/*COMMENT_MULTI*/ && token.name != 10/*LINETERMINATOR*/) {
					noWhite.push(token);
				}
			}
		}
		return noWhite||all;
	},
	//#endif

	parse: function(){
		this.tokenizer = new Tokenizer(this.lastInput);
		this.tree = [];
		this.wtree = this.tokenizer.wtree;
		this.btree = this.tokenizer.btree;
		this.parser = new ZeParser(this.lastInput, this.tokenizer, this.tree);
		this.parser.parse();
		this.hasError = this.tokenizer.errorStack.length || this.parser.errorStack.length;
	},

	startProcess: function(){
		this.reset();

		// we need the global scope to track the implicit globals we encounter
		var globalScope = this.globalScope = this.tree.scope;

		// adds known "auto-globals" to the global scope. prevents lookup misses
		this.addEcmaBuiltIns(globalScope);

		// actual post-processing
		var start = Date.now();
		this.phase1(this.tree);
		var one = Date.now() - start;
		
		start = Date.now();
		this.phase2(this.tree);
		var two = Date.now() - start;
		
		start = Date.now();
		this.phase3(this.tree);
		var three = Date.now() - start;

		start = Date.now();
		this.extraTyping(this.tree);
		var four = Date.now() - start;

		this._currentLine.stop = this.lastInput.length;
		delete this._currentLine;
		
		return [one,two,three,four];
	},
	reset: function(){
		// root of recursive call tree
		this._currentLine = [];
		this._currentLine.lineId = 0;
		this._currentLine.start = 0;
		this.lines = [this._currentLine];

		// problem tracking
		this.collects = {
			errors: [],
			warnings: [],
			implicitGlobals: [], // all unexpected variable references which can not be found in a scope reachable from that position
			knownGlobals: [], // all ecma and browser globals
			jsdocs: [],
			functions: [],
			objlits: [],
			arrlits: [],
			todofix: [], // todo, tofix, fixme
			pragmas: [], // define, ifdef, endif, macro, inline, endline
			defines: [] // collection of defined tokens with #define <token>
		};

		this.pragmas = {
			ifdefs: [],
			inlines: [],
			inlineNames: [],
			macros: []
		};
	},
	addEcmaBuiltIns: function(scope){
		// this is not everything, just some more common ones. common dom apis, html5 apis, etc.
		// http://code.google.com/p/closure-compiler/source/browse/#svn%2Ftrunk%2Fexterns

		var nodejs = [
			['require','Function'],
			['module','Object',[
				['exports','Object']
			]]
		];

		// http://www.whatwg.org/specs/web-apps/current-work/multipage/browsers.html#window
		var browsers = [
			['window','Object',null,null,true],
			['global','Object',null,null,true],
			// Timer api
			[[
				'setTimeout',
				'clearTimeout',
				'setInterval',
				'clearInterval'
			],'Function'],
			// File api (and related)
			[[
				'Blob',
				'File',
				'FileError',
				'FileList',
				'FileReader'
			],'Function', null, true],
			// crypto
			['crypto','Function'],
			// web sockets
			['WebSocket','Function', null, true],
			// ajax
			[[
				'XMLHttpRequest',
				'XDomainRequest',
				'XMLHttpRequestUpload'
			],'Function', null, true],
			// storage
			[[
				'globalStorage',
				'localStorage',
				'openDatabase',
				'sessionStorage',
				'applicationCache'
			],'Function'],
			// web workers
			[[
				'Worker',
				'MessageChannel',
				'MessageEvent',
				'MessagePort',
				'SharedWorker'
			],'Function', null, true],
			['postMessage','Function'], // not a constructor
			// general
			[[
				'ActiveXObject',
				'addEventListener',
				'alert',
				'atob',
				'attachEvent',
				'back',
				'blur',
				'btoa',
				'close',
				'confirm',
				'detachEvent',
				'dispatchEvent',
				'escape',
				'execScript',
				'find',
				'focus',
				'forward',
				'getAttention',
				'getComputedStyle',
				'getSelection',
				'home',
				'Image',
				'innerHeight',
				'innerWidth',
				'open',
				'openDialog',
				'outerHeight',
				'outerWidth',
				'pageXOffset',
				'pageYOffset',
				'print',
				'prompt',
				'Range',
				'releaseEvents',
				'removeEventListener',
				'scrollByLines',
				'scrollByPages',
				'scrollX',
				'scrollY',
				'Selection',
				'showModelDialog',
				'stop',
				'TimeRanges',
				'unescape',
				'XMLDOMDocument'
			],'Function'],
			[[
				'console',
				'document',
				'event',
				'external',
				'find',
				'frameElement',
				'frames',
				'history',
				'length',
				'location',
				'locationbar',
				'menubar',
				'navigator',
				'name',
				'opener',
				'parent',
				'personalbar',
				'screen',
				'screenX',
				'screenY',
				'scrollbars',
				'self',
				'status',
				'statusbar',
				'toolbar',
				'top',
				'undoManager'
			],'Object']
		];
		var ecmas = [
			['NaN','number'],
			['Infinity','number'],
			['undefined','undefined'],
			['eval','Function'],
			['parseInt','Function'],
			['parseFloat','Function'],
			['isNaN','Function'],
			['isFinite','Function'],
			['decodeURI','Function'],
			['decodeURIComponent','Function'],
			['encodeURI','Function'],
			['encodeURIComponent','Function'],
			['String','Function',[
				['prototype','Object',[
					[[
						'toString',
						'valueOf',
						'charAt',
						'charCodeAt',
						'concat',
						'indexOf',
						'lastIndexOf',
						'localeCompare',
						'match',
						'replace',
						'search',
						'slice',
						'splice',
						'substring',
						'toLowerCase',
						'toLocaleLowerCase',
						'toUpperCase',
						'toLocaleUpperCase',
						'trim'
					], 'Function']
				]],
				['fromCharCode', 'Function'],
				['length', 'number']
			], true],
			['Number', 'Function', [
				['prototype', 'Object', [
					[[
						'toString',
						'valueOf',
						'toLocaleString',
						'toFixed',
						'toExponential',
						'toPrecision'
					], 'Function']
				]],
				[[
					'MIN_VALUE',
					'MAX_VALUE',
					'NaN',
					'POSITIVE_INFINITY',
					'NEGATIVE_INFINITY'
				], 'number']
			], true],
			['Boolean','Function',[
				['prototype','Object',[
					[['toString','valueOf'],'Function']
				]]
			],true],
			['Function','Function',[
				['prototype','Object',[
					[[
						'toString',
						'apply',
						'call',
						'bind'
					],'Function']
				]]
			],true],
			['Array','Function',[
				['prototype','Object',[
					[[
						'toString',
						'toLocaleString',
						'concat',
						'join',
						'pop',
						'push',
						'reverse',
						'shift',
						'slice',
						'sort',
						'splice',
						'unshift',
						'indexOf',
						'lastIndexOf',
						'every',
						'some',
						'forEach',
						'map',
						'filter',
						'reduce',
						'reduceRight'
					],'Function']
				]],
				['isArray','Function'],
				['length','number']
			],true],
			['RegExp','Function',[
				['prototype','Object',[
					[['exec','test','toString'],'Function']
				]],
				['length','number']
			],true],
			['Date','Function',[
				['prototype','Object',[
					[[
						'toString',
						'toDateString',
						'toTimeString',
						'toLocaleString',
						'toLocaleDateString',
						'toLocaleTimeString',
						'valueOf',
						'getTime',
						'getFullYear',
						'getUTCFullYear',
						'getMonth',
						'getUTCMonth',
						'getDate',
						'getUTCDate',
						'getDay',
						'getUTCDay',
						'getHours',
						'getUTCHours',
						'getMinutes',
						'getUTCMinutes',
						'getSeconds',
						'getUTCSeconds',
						'getMilliseconds',
						'getUTCMilliseconds',
						'getTimezoneOffset',
						'setTime',
						'setMilliseconds',
						'setUTCMilliseconds',
						'setSeconds',
						'setUTCSeconds',
						'setMinutes',
						'setUTCMinutes',
						'setHours',
						'setUTCHours',
						'setDate',
						'setUTCDate',
						'setMonth',
						'setUTCMonth',
						'setFullYear',
						'setUTCFullYear',
						'toUTCString',
						'toISOString',
						'toJSON'
					],'Function']
				]],
				['length','number'],
				['parse','Function'],
				['UTC','Function'],
				['now','Function']
			],true],
			['Math','Object',[
				[[
					'E',
					'LN10',
					'LN2',
					'LOG2E',
					'LOG10E',
					'PI',
					'SQRT1_2',
					'SQRT2'
				],'number'],
				[[
					'abs',
					'acos',
					'asin',
					'atan',
					'atan2',
					'ceil',
					'cos',
					'exp',
					'floor',
					'log',
					'max',
					'min',
					'pow',
					'random',
					'round',
					'sin',
					'sqrt',
					'tan'
				],'Function']
			]],
			['JSON','Object',[
				['parse','Function'],
				['stringify','Function']
			]],
			['Object','Function',[
				['prototype','Object',[
					[[
						'toString',
						'toLocaleString',
						'valueOf',
						'hasOwnProperty',
						'isPrototypeOf',
						'propertyIsEnumerable'
					],'Function']
				]],
				[[
					'getPrototypeOf',
					'create',
					'defineProperty',
					'defineProperties',
					'seal',
					'freeze',
					'preventExtensions',
					'isSealed',
					'isFrozen',
					'isExtensible',
					'keys'
				],'Function']
			],true]
		];

		var addProperties = function(props, obj){
			obj.properties = {};
			props.forEach(function(prop){
				if (typeof prop[0] == 'string') prop[0] = [prop[0]]; // make sure the first item is an array (of names)
				prop[0].forEach(function(name){
					var probj = {
						value: name,
						varType: [prop[1]]
					};
					if (prop[2]) addProperties(prop[2], probj);
					obj.properties[name] = probj;
				});
			});
		};
		var addGlobal = function(item,forBrowser){
			if (!(item[0] instanceof Array)) item[0] = [item[0]];
			item[0].forEach(function(name){
				var obj = {
					implicit: true,
					isDeclared: true,
					value: name,
					varType: [item[1]]
				};
				if (forBrowser) obj.isBrowser = true;
				else obj.isEcma = true;
				// properties
				if (item[2]) addProperties(item[2], obj);
				// is constructor
				if (item[3]) {
					obj.isConstructor = true;
					obj.constructorName = item[0];
				}
				// is global
				if (item[4]) obj.isGlobalObject = true;
				scope.push(obj);
			});
		};

		browsers.forEach(function(o){ addGlobal(o, true); });
		nodejs.forEach(function(o){ addGlobal(o, true); });
		ecmas.forEach(function(o){ addGlobal(o); });
	},

	addWarning: function(token, msg){
		if (!token.warning) token.warning = msg;
		if (!token.warnings) token.warnings = [];
		token.warnings.push(msg);
	},
	hasWarning: function(token, msg){
		return token.warning == msg || (token.warnings && token.warnings.indexOf(msg) >= 0);
	},

	/**
	 * Post parser processing. First phase. Takes care of anything that
	 * doesn't require knowledge about the future of the code. This includes:
	 * - makes scope contents unique
	 * - disambiguates expressions
	 * - jsdoc attachment (processing in phase 2)
	 * - some warning detection
	 * - make sure token.value is present on all tokens
	 * - fill both linear trees
	 * - assign line number per token
	 * - collect special tokens (asis, jsdocs, etc)
	 * - process variable declarations
	 * - process lead values
	 * - make sure all lead values and declared vars have a trackingObject
	 * - check identifiers for being known (ecma, browser, dev, etc)
	 * - do line administration
	 * 
	 * @param {Object[]} stack Branch of the parse tree
	 * @param {Object[]} [_scope] Contains all variables and outer scopes reachable from current code (inc. this and if applicable also arguments)
	 * @param {String[]} [_labels] Contains all valid labels from the current code (break, continue)
	 * @param {boolean} [_insideConditional] Are we currently inside  condition? like the if or while statement header... warning stuff for =
	 * @param {boolean} [_insideFunction] Are we currently inside any function? Check for return keyword
	 * @param {boolean} [_insideSwitch] Are we currently inside a switch? Allows anonymous break
	 * @param {boolean} [_insideIteration] Are we currently inside an iteration? Allows anonymous break and continue and warns for functions
	 * @param {number} [_phase] Indicates whether we want to parse a string, var, func or other
	 */
	phase1: function(stack, _scope, _labels, _insideConditional, _insideFunction, _insideSwitch, _insideIteration, _phase){
		if (!_phase) _phase = 0;

		// all variables have been tracked and locked into their corresponding scope by the parser
		if (stack.scope) {
			// entering new scope
			_scope = stack.scope;
			// in theory, all scopes are only reached and entered once...
			this.processScope(_scope);
		}
		// same goes for labels, all accounted for by the parser
		if (stack.labels) {
			// entering new statement / function
			_labels = stack.labels;
		}

		// do various semantic environment state administration
		if (stack.isIteration) _insideIteration = true;
		else if (stack.sub == 'switch') {
			_insideSwitch = true;
			stack.stops = 0; // default
			stack.maxStops = null; // if not null at the end, use this number instead
		// when entering a function, we clear the semantic environment when going deeper
		} else if (stack.isFunction) {
			_insideConditional = false;
			_insideSwitch = false;
			_insideIteration = false;
			_insideFunction = true;

			// reset phase (directives, hoisting)
			_phase = 0;
		}

		// tracks last clauses for a switch to assign flow-stop (since they're not grouped)
		var lastClause = null;

		stack.forEach(function(token, index){
			if (token instanceof Array) {
				// remember clause branches to mark them as breaking the flow
				if (token.sub == 'case' || token.sub == 'default') lastClause = this.enteringNewClause(token, stack, lastClause, index);

				this.phase1Stack(token, stack, index, _scope, _labels, _insideConditional, _insideFunction, _insideSwitch, _insideIteration);

				// deal with directives and with the grouping of function and var declarations
				if (stack.root || stack.isFunction) _phase = this.handlePhase(token, stack, _phase);
				lastClause = this.determineFlowBreakers(token, stack, lastClause);
			} else {
				if (token.statementHeaderStart && stack.forType != 'each') _insideConditional = true;
				else if (token.statementHeaderStop) _insideConditional = false;
				// for header is only the second part
				else if (token.forEachHeaderStart) _insideConditional = true;
				else if (token.forEachHeaderStop) _insideConditional = false;

				this.phase1Token(token, stack, index, _scope, _labels, _insideConditional, _insideFunction, _insideSwitch, _insideIteration);
			}
		}, this);

		this.handleSwitchFlowBreakers(stack, lastClause);
	},
	phase1Stack: function(stack, parent, index, _scope, _labels, _insideConditional, _insideFunction, _insideSwitch, _insideIteration){
		if (stack.sub == 'switch') {
			stack.cases = 0;
			stack.hasDefault = false;
		}

		if (stack.sub == 'block') stack.statements = 0;

		// make sure every expression contains 1 or 3 elements. no operator amibguity for the parse tree
		this.disambiguateOperators(stack);

		if (parent.sub == 'block' && stack.desc == 'statement-parent') ++stack.statements;

		// an edge case with the dead code detection. if the try blocks were dead before even entering the try, everything is dead code regardless.
		// but since the detection runs on the way down, we need to check this on the way up. that's what we do here :)
		if (parent.sub != 'try' || parent.flowOnArrival) stack.stops = stack.flowOnArrival = Math.max(parent.flowOnArrival|0, parent.stops|0);

		// here we simply hook the jsdoc statement to the next function or var-keyword
		// if functions or multiple var decls need access to it, they can find it through stack.varStack.lastJsdoc
		if (this.lastJsdoc && (stack.isFunction || stack.desc == 'var decl')) {
			// hook last unused jsdoc to this group for later processing
			stack.lastJsdoc = this.lastJsdoc;
			if (stack.isFunction && stack[0] && stack[0].value == 'function') stack[0].jsdoc = this.lastJsdoc; // for auto generation
			else if (stack.desc == 'var decl') stack[0].jsdoc = this.lastJsdoc; // for auto generation
			// prevent next statement from binding this jsdoc
			this.lastJsdoc = null;
			// the jsdoc will be processed in phase 2, when we know all tracking objects have been attached
		}

		// useless blocks?
		if (stack.isEmptyBlock) this.addWarning(this.btree[stack.nextBlack], 'empty block');
		// do not create functions inside iterators
		if (_insideIteration && stack.isFunction) this.addWarning(this.btree[stack.nextBlack], 'iteration function');

		// just process the contents of this array
		this.phase1(stack, _scope, _labels, _insideConditional, _insideFunction, _insideSwitch, _insideIteration);

		// collect the properties of an object literal (not their types yet). Do after to make sure all .value have been set (number/string)
		if (stack.hasObjectLiteral) this.collectObjectLiteralProperties(stack);

		if (stack.hasArrayLiteral) this.getArrayLiteralTypes(stack);

		if (!stack.stops) {
			if (stack.sub == 'return') stack.stops = this.IS_RETURN; // effect stops at function boundary
			else if (stack.sub == 'throw') stack.stops = this.IS_THROW; // effect stops at try
			else if (stack.sub == 'break') {
				if (stack.hasLabel) stack.stops = this.IS_LABELED_BREAK; // effect stops at switch/iteration boundary
				else stack.stops = this.IS_BREAK; // effect stops at switch/iteration boundary
			}
			else if (stack.sub == 'continue') stack.stops = this.IS_CONTINUE; // effect stops at switch/iteration boundary
			else stack.stops = 0;
		}
	},
	phase1Token: function(token, stack, index, _scope, _labels, _insideConditional, _insideFunction, _insideSwitch, _insideIteration){
		token.len = token.stop - token.start; // this is not the number of cols!

		// make sure all matches have a .value property so we dont have to substring anymore anywhere
		if (!token.value) token.value = this.lastInput.substring(token.start, token.stop);

		if (token.isFuncDeclKeyword || token.isFuncExprKeyword) this.collects.functions.push(token);

		// important indexing of lines and position
		token.startLineId = this._currentLine.lineId;
		this._currentLine.push(token); // used for pos2token computation

		// set jsdoc candidate
		if (token.name == 8/*COMMENT_MULTI*/ && token.value[2] == '*') this.lastJsdoc = token;

		if (token.name == 14/*error*/) this.collects.errors.push(token);
		if (token.isObjectLiteralStart) this.collects.objlits.push(token);
		if (token.isArrayLiteralStart) this.collects.arrlits.push(token);

		// detect and mark constructors, and detect missing parens from new (do before hooking up tracking object and scope!)
		if (token.value == 'new') this.processNew(stack, index);

		if (token.name == 2/*identifier*/) {
			if (token.meta) this.processIdentifier(token, _scope); // also does check keyword
			if (!token.checkedKeywords) this.checkKnownKeywords(token, true);
		}

		// track properties of this lead value, if any. do in phase 2 because it relies on the tree to be built
		if (token.isObjectLiteralStart || token.isArrayLiteralStart || token.leadValue) this.trackProperties(token, stack);

		if (token.meta == 'func expr name') token.trackingObject.isFuncExprName = true;

		// the parser has already determined whether there are any newlines
		// in this token. it must because asi might be applied because of it
		if (token.hasNewline) {
			// this is some tricky shit, but we must analyze the token in more detail to determine actual start/stops per line
			var lastNewlinePos = token.start;
			while ((lastNewlinePos=this.lastInput.indexOf('\n', lastNewlinePos)) >= 0 && lastNewlinePos < token.stop) {
				++lastNewlinePos;
				this._currentLine.stop = lastNewlinePos;
				this._currentLine = [];
				this._currentLine.lineId = this.lines.length;
				this._currentLine.coveredBytoken = token; // lines completely inside a comment/string need to know which match
				this._currentLine.start = lastNewlinePos;
				this.lines.push(this._currentLine);
			}
		}

		// find (todo, tofix, fixme) and pragmas
		if (token.name == 6/*COMMENT_MULTI*/ || token.name == 7/*COMMENT_SINGLE*/) {
			if (this.regexToDoFix.test(token.value)) {
				this.collects.todofix.push(token);
				token.hasTodofix = true;
			}
		}
	},

	processScope: function(scope){
		// remember the scopes (mainly used for minification)
		this.scopes.push(scope);

		// filter arr and make sure it only has unique occurrences
		for (var i=0; i<scope.length-1; ++i) {
			if (scope[i] instanceof Array) {
				scope.upper = scope[i];
			} else {
				var j = scope.length;
				while (--j > i) {
					if (scope[i].value == scope[j].value) {
						scope.splice(j, 1);
					}
				}
			}
		}
		// arr should now only contain unique var names
	},
	checkKnownKeywords: function(token, notOnTracking){
		if (notOnTracking) var target = token;
		else var target = token.trackingObject;

		if (token.isDevRelic == null && this.regexDevSigns.test(token.value)) {
			token.isDevRelic = true;
		}

		if (!token.checkedKeywords) {
			if (!token.isPropertyName) {
				// this is an ecmascript built in name
				if (target.isEcma == null && this.regexEcma.test(token.value)) {
					target.isEcma = true;
					if (!notOnTracking) token.isEcma = true;
				}
				// this is a name used in a browser context (document, window, etc)
				if (target.isBrowser == null && this.regexBrowser.test(token.value)) {
					target.isBrowser = true;
					if (!notOnTracking) token.isBrowser = true;
				}
			}

			token.checkedKeywords = true;
		}
	},
	processVarCreate: function(token, scope){
		// names of function declarations are declared in the scope above the current scope
		var funcDecl = token.meta == 'func decl name';
		if (funcDecl) scope = scope[0];

		// mark this variable as being declared in the scope (or mark it as doubly declared, if already marked as declared)
		this.markVarAsDeclaredAndFixRefs(scope, token, funcDecl);
	},
	markVarAsDeclaredAndFixRefs: function(scope, target, funcDecl){ // mark var as declared or as doubly declared
		// catchScope: -1, 0 or 1. only if -1, use one scope higher to search
		// catch scopes can only record their (only) argument. other declarations go into their parent
		while (scope.catchScope < 0) {
			if (!(scope[0] instanceof Array)) throw "catch scope should have upper scope as first kid";
			scope = scope[0];
		}
		if (scope.catchScope > 0) scope.catchScope = -1; // either target gets recorded or we die.

		for (var i=0; i<scope.length; ++i) {
			var trackingObject = scope[i];
			// the parser will have added this variable to the current scope level if it was declared here
			// so we dont need to check arrays, the var will exist on this level.
			if (!(trackingObject instanceof Array)) {
				if (trackingObject.implicit) {
					if (!this.hasError) {
						console.log('for next throw:',[this.lastInput]);
						throw 'the parser should have declared it';
					}
				}
				if (trackingObject.value == target.value) {
					if (funcDecl) trackingObject.isFuncDecl = true;

					if (target.functionStack) trackingObject.functionStack = target.functionStack;
					target.targetScope = scope; // ref to enclosing scope (for jspath, et.al.)
					// if target already had some type(s) assigned to it by the parser, copy those here.
					if (target.varType) trackingObject.varType = target.varType.slice(0);

					if (!trackingObject.isDeclared) trackingObject.isDeclared = true;
					else trackingObject.isDoublyDeclared = target.wasAlreadyDeclared = true;

					// create reference to this "shared" object with some variable information
					target.trackingObject = trackingObject;
					// create reference on scope object. this way we can easily find all refs to this var.
					if (!trackingObject.refs) trackingObject.refs = [];
					trackingObject.refs.push(target);

					trackingObject.isCatchVar = target.isCatchVar;

					return;
				}
			}
		}
		if (!this.hasError) {
			console.log(['Error details:', target, scope, funcDecl, this.lastInput]);
			throw "Post processing error. The variable was declared but not found in this scope level.";
		}
	},
	processLeadValue: function(token, scope){
		// search the scope tree for a declaration (scope is build during parsing, so process can safely assuming a miss if not found)
		token.scopeLookupDepth = this.findInScope(scope, token);
		// negative means not found (indicates depth to global scope)
		if (token.scopeLookupDepth < 0) {
			// add implicits so we can link their meta data, regardless of the absence of their declaration
			var newScopeObject = {value:token.value, implicit: true, isDeclared:false};
			this.globalScope.push(newScopeObject);
		}
		// make sure the declaration of this variable comes before its usage
		this.markVarUsageFixRefsAndCheckPremature(scope, token);
	},
	findInScope: function(scope, target, _depth){ // get scope lookup depth
		if (!_depth) _depth = 1;
		var upperScope = null;
		for (var i=0; i<scope.length; ++i) {
			// first scan current scope, then try upper
			if (scope[i] instanceof Array) upperScope = scope[i];
			else if (scope[i].value == target.value) {
				if (scope[i].implicit) return -_depth; // no, you actually didnt find it
				return _depth;
			}
		}
		// not found in current scope, try upper if found
		if (upperScope) return this.findInScope(upperScope, target, _depth+1);
		return -_depth;
	},
	markVarUsageFixRefsAndCheckPremature: function(scope, target){ // check whether var is declared at this point
		var upperScope; // remember upper scope. we'll check it if we cant find target in this scope level
		for (var i=0; i<scope.length; ++i) {
			var trackingObject = scope[i];
			if (trackingObject instanceof Array) upperScope = trackingObject;
			else if (trackingObject.value == target.value) {
				// create reference to this "shared" object with some variable information
				target.trackingObject = trackingObject;

				target.targetScope = scope; // ref to enclosing scope (for jspath, et.al.)

				// if it has a ref to a function decl, copy that to the tracking obj
				if (target.functionStack) trackingObject.functionStack = target.functionStack;

				// if target already had some type(s) assigned to it by the parser, copy those here.
				if (target.varType) trackingObject.varType = target.varType.slice(0);

				// create reference on scope object. this way we can easily find all refs to this var.
				if (!trackingObject.refs) trackingObject.refs = [];
				trackingObject.refs.push(target);

				trackingObject.used = true;

				if (!trackingObject.isDeclared && !trackingObject.implicit && !trackingObject.isFuncDecl) {
					target.prematureUsage = true;
				}

				return;
			}
		}
		// not found in current scope. if upper scope found, try to search in there now.
		if (upperScope) return this.markVarUsageFixRefsAndCheckPremature(upperScope, target);
		if (!this.hasError) console.log('error:',target,[this.lastInput]);
		throw 'any variable, declared or undeclared, should be logged into the scope at this point';
	},
	collectObjectLiteralProperties: function(stack){
		var props = stack.definedProperties = {};
		// walk through every pair
		for (var i=0; i<stack.length; ++i) {
			if (stack[i].isObjectLiteralStart) stack[i].definedProperties = props;
			else if (stack[i].isObjectLiteralPair) {
				var nameIndex = 0;
				var valueIndex = 2;
				// process the pair. should be normalized to three elements by the parser: name, colon, expressions
				// we want only the name, for now. and maybe hook up a ref to the expressions for typing later?
				var nameToken = stack[i][nameIndex][0];

				if (stack[i][nameIndex].isWhite) {
					if (!this.hasError) console.log("if this is not an accessor, check me",this.lastInput);
					continue; // TOFIX: accessor property
				}

				if (nameToken.value == '__proto__') {
					nameToken.value = '__proTo__';
					this.addWarning(nameToken, 'dont use __proto__');
				}

				// if a prop was defined twice, only remember last instance. first instance will be overwritten (dead code..)
				if (this.hasOwn(props, nameToken.value)) {
					props[nameToken.value].propOverwritten = true;
					this.addWarning(props[nameToken.value], 'dead code');
					this.addWarning(nameToken, 'duplicate objlit prop');
				}
				props[nameToken.value] = nameToken;
				// there must be exactly one expression (no comma), create ref to it
				nameToken.assignedPropExpression = stack[i][valueIndex] && stack[i][valueIndex][0];
			}
		}
	},
	getArrayLiteralTypes: function(stack){
		var start = this.btree[stack.nextBlack];
		if (!start.trackingObject) start.trackingObject = {};
		stack.forEach(function(e){
			if (e.desc == 'expressions') {
				var type = this.getType(e);
				if (!(type instanceof Array)) type = [type];
				if (!start.trackingObject.arrayTypes) start.trackingObject.arrayTypes = [];
				type.forEach(function(t){
					if (typeof t == 'string' && start.trackingObject.arrayTypes.indexOf(t) < 0) start.trackingObject.arrayTypes.push(t);
					else if (t.isArrayLiteralStart) start.trackingObject.arrayTypes.push('Array');
					else if (t.isObjectLiteralStart) start.trackingObject.arrayTypes.push('Object');
				});
			}
		},this);
	},
	disambiguateOperators: function(stack){
		// operators are either left- or right-associative. in js they are 
		// all left-associative except for assignments and the ternary.
		// we get the ternary for free through the way it's parsed, so we
		// only have to take care about the assignments.
		// basically what we do here is check out all the operators
		// try to get two that have different precedence, cut there
		// if not, group the set according to associativity. meaning:
		// a+b+c+d becomes ((a+b)+c)+d
		// a+a+a^a+a+a becomes ((a+a)+a)^((a+a)+a)
		// a=b=c becomes a=(b=c) (assignment is the only exception, for our case at least)

		if (stack.desc == 'expression') {
			// scan all operators. remember the most important one.
			// if same level, keep left-most (even for assignments)
			while (stack.length % 2 == 1 && stack.length > 3 && stack[1].isBinaryOperator) {
				var minTarget = 1;
				stack[1][0].isAmbiguous = true;
				var minValue = this.precedence[stack[1].sub];
				var n = 3;
				var wasLess = false;
				while (n < stack.length-1 && stack[n][0]) { // check for stack[n][0] is because it might not exist if an error was thrown.
					stack[n][0].isAmbiguous = true;
					var curValue = this.precedence[stack[n].sub];
					if (curValue < minValue || (curValue == minValue && (stack[n].isAssignment || stack[n].sub == '?' || stack[n].sub == ':'))) {
						minValue = curValue;
						minTarget = n;
						wasLess = true;
					}
					n += 2;
				}

				// minTarget is now our next "cut"
				// take the token directly left and right of the operator
				var newExpr = [stack[minTarget-1], stack[minTarget], stack[minTarget+1]];
				newExpr.desc = 'expression';
				newExpr.wasDisambiguated = true;
				newExpr.sub = stack[minTarget].sub;
				newExpr.nextBlack = stack[minTarget-1].nextBlack;
				stack.splice(minTarget-1, 3, newExpr);
				// correct type of expression for parent expression
				stack.sub = stack[1].sub;
			}
		} else if (stack.length == 3 && stack[1].isBinaryOperator) { // still expression
			stack.sub = stack[1].value; // operator name
		}
	},
	processNew: function(stack, index){
		var newToken = stack[index];
		// collect the new of the constructor (in most cases, just one identifier)
		// new is only weaker than . and [], so we stop at parens or postfix
		var token;
		while ((token=stack[++index]) && token.isWhite);
		if (!token) return;
		var lead = token;
		if (token.name != 2/*identifier*/) return; // unable to continue...
		var objName = token.value;
		var lastDirectProperty = token;
		// now keep processing .x or [x] until you reach an opening paren, the end or something unexpected
		while (token = stack[++index]) {
			if (!token.isWhite) {
				if (token.value == '.') {
					while ((token = stack[++index]) && token.isWhite);
					if (token && token.name != 2/*identifier*/) return; // unable to continue...
					objName += '.'+token.value;
					lastDirectProperty = token;
				} else if (token.value == '[') {
					while ((token = stack[++index]) && token.isWhite);
					if (token && token.desc != 'expressions') return; // unable to continue...
					while ((token = stack[++index]) && token.isWhite);
					if (token && token.value != ']') return; // unable to coninue
					objName += '[]';
					lastDirectProperty = null;
				} else if (token.value == '(') {
					break;
				} else {
					this.addWarning(newToken, 'new wants parens');
					break;
				}
			}
		}

		if (!token) this.addWarning(newToken, 'new wants parens');

		if (lastDirectProperty) {
			if (lastDirectProperty.value[0] < 'A' || lastDirectProperty.value[0] > 'Z') {
				this.addWarning(lead, 'use capital namespacing');
			}

			lastDirectProperty.isConstructor = true;
			lastDirectProperty.constructorName = objName;

			if (lastDirectProperty.functionStack) {
				lastDirectProperty.functionStack.isConstructor = true;
				lastDirectProperty.functionStack.constructorName = objName;
			}

			// set ref ready for assignment later
			newToken.targetExpression = lastDirectProperty;
		} else {
			// unable to determine target at this point
			newToken.targetExpression = false;
		}
	},
	trackProperties: function(token){
		var next = token;
		var lastFound = token;

		if (!token.isObjectLiteralStart && !token.isArrayLiteralStart && !token.leadValue) {
			if (!this.hasError) throw console.log('error:',token,[this.lastInput]),"why?";
			return;
		}
		if (token.isString || token.isNumber || token.name == 1/*reg_exp*/) return; // no need to grep properties of primitives
		// for every property encountered, this should be the first time it's processed by this function.

		// keep going right while there is a dot. stop at "eol", ( or [. anything else is weird.
		var prev = token;
		var lastKnownProperty = prev;

		// lead value might be an array or object...
		if (token.isObjectLiteralStart || token.isArrayLiteralStart) {
			if (!next.twin) return;
			// jump to closer of literal
			next = next.twin;
		}

		// track what the trailing part of this "value" is going to be. nothing, dot property, dynamic property, call, ++, --
		// values: lead, dot, dynamic, call, +-, ?
		token.valueType = 'lead';

		var prevTrackingObject;
		next = this.btree[next.tokposb+1];
		while (next && next.value == '.') {
			next = this.btree[next.tokposb+1];
			token.valueType = 'dot';

			if (next) {
				lastFound = next;
				if (next.value == '__proto__') {
					next.value = '__proTo__'; // this magic fuckup causes problems.
					this.addWarning(next, 'dont use __proto__');
				}

				next.isPropertyOf = prev;
				lastKnownProperty = next;

				prevTrackingObject = prev.trackingObject;
				if (prevTrackingObject && prevTrackingObject.isGlobalObject) {
					// turn next into a global variable...
					next.isPropertyName = false; // global variable
					this.processLeadValue(next, this.globalScope);
				} else {
					if (prevTrackingObject) { // might be false for array or object literal
						// make sure it has a properties array
						if (!prevTrackingObject.properties) prevTrackingObject.properties = {};
						var props = prevTrackingObject.properties;

						// look through the properties object and see whether it was found before
						// (nvm) (this is why we use Object.create(null), else names that would exist on Object prototype would also always evaluate to true here)
						if (this.hasOwn(props,next.value)) {
							if (!props[next.value].refs) props[next.value].refs = []; // happens for built-in properties
							props[next.value].refs.push(next);
							// hook _property_ tracking object
							next.trackingObject = props[next.value];
						} else {
							// property not yet found for current parent (prev), make it
							next.trackingObject = {value:next.value, refs:[next]};
							props[next.value] = next.trackingObject;
						}

						if (next.isConstructor) {
							next.trackingObject.isConstructor = true;
							next.trackingObject.constructorName = next.constructorName;
						}
					} else {
						// oh i know this is going to be a very very annoying bug in the future :(
						// but this thing is going to need a tracking object, one way or the other
						next.trackingObject = {value:next.value, refs:[next]};
					}

					if (next.value == 'call' || next.value == 'apply') {
						if (prev.isObjectLiteralStart || prev.isArrayLiteralStart || prev.isPrimitive) {
							this.addWarning(prev, 'cannot call/apply that');
						} else {
							// call and apply are magic methods of functions. there's no real point of having a property
							// with this name for other reasons (although it's not impossible). lets mark prev as a function
							this.setTypeToRef(prev, 'Function');
						}
					} else if (next.value == 'prototype') {
						// mark prev as constructor
						if (!prev.isConstructor) {
							// the only reason to mess with prototype is to change a constructor or
							// to copy the prototype of another constructor. either way, you're
							// very very likely to only take that property of a constructor.
							prev.isConstructor = true;
							prev.constructorName = prev.value; // TOFIX: this is not quite correct yet
						}
						if (prev.trackingObject && !prev.trackingObject.isConstructor) {
							// the only reason to mess with prototype is to change a constructor or
							// to copy the prototype of another constructor. either way, you're
							// very very likely to only take that property of a constructor.
							prev.trackingObject.isConstructor = true;
							prev.trackingObject.constructorName = prev.value; // TOFIX: this is not quite correct yet
						}

						// constructors are typeof Function themself
						this.setTypeToRef(prev, 'Function');
					} else if (!token.isObjectLiteralStart && !token.isArrayLiteralStart) {
						// do not set object if the property name in combination with a primitive was something known
						if (
							next.value == 'toString' || next.value == 'toFixed' || next.value == 'toPrecision' || next.value == 'toExponential' || // number (gosh)
							next.value == 'toUpperCase' || next.value == 'toLowerCase' || next.value == 'substring' || next.value == 'substr' || next.value == 'slice' || next.value == 'search' || next.value == 'replace' || next.value == 'match' || next.value == 'indexOf' || next.value == 'lastIndexOf' || next.value == 'charCodeAt' || next.value == 'charAt' // string (gosh)
						) {
							next.isObjectUnlessPrimitive = true; // need to verify this later, after type checking (varType) is done.
						} else {
							// since this is a property access, it is very probable that prev is an object, unless of course it's an array
							prev.isObjectUnlessArray = true;
						}
					}
				}
			}

			prev = next;
			next = this.btree[next.tokposb+1];
		}

		token.lastDotProperty = lastKnownProperty;

		if (next && (next.isObjectLiteralStart || next.isArrayLiteralStart)) {
			// as expected, no properties of the array or object
			token.leadValueTarget = true;
		} else if (next && next.value == '[') {
			// array and object literals will not have a tracking object. also, we _know_ their type anyways.
			if (!prev.isArrayLiteralStart && !prev.isObjectLiteralStart) {
				// we try to set this type to an object, but later when we processed assignments. if the dynamic access was on an array, it shouldnt become an object now
				prev.dynamicPropertyAccess = true;
				token.leadValueTarget = false; // unable to safely determine right side of lead value (for assignment or whatever)
				token.hasDynamicProperty = true;
			}
		} else if (next && next.value == '(') {
			if (prev.isObjectLiteralStart || prev.isArrayLiteralStart || prev.isPrimitive) {
				this.addWarning(token, 'cannot call that');
			} else {
				// since this is a property access, push object on the type stack for this var
				this.setTypeToRef(prev, 'Function');
				prev.tokenIsCalled = true;
				token.leadValueTarget = null; // unable to safely determine right side of lead value (for assignment or whatever), null signifies a call
				token.hasCallExpression = true;
			}
		} else if (next && (next.value == '++' || next.value == '--')) {
			// postfix increment. mark token as such. prev is also considered a number now (postfix assigns number back)
			// note: the return type of the postfix expr is _always_ a number, regardless of the lhs
			token.hasPostfix = true;
			prev.postfixed = true;
			token.leadValueTarget = false;
			next.hasBeenAssigned = true; // prevent unnecessary computation.
			this.setTypeToRef(prev, 'number');
		} else {
			// given the lead value, mark the targeted property (or true if it there is no property at all)
			// it will be false if there was a problem (dynamic property or call expression).
			if (prev == token) token.leadValueTarget = true;
			else token.leadValueTarget = prev;
		}

		// continue scanning for properties, needed for determining proper assignment stuff
		// give any in-between property names found type of object, even though we cant really do anything else with them.
		while (next && (next.value == '.' || next.value == '(' || next.value == '[')) {
			var last = lastFound; // we need this for refs, but we need to clear lastFound. annoying, i know.
			if (lastFound) {
				if (!lastFound.trackingObject) lastFound.trackingObject = {};
				if (next.value == '(') this.setTypeToRef(lastFound, 'Function');
				else if (next.value == '[') lastFound.isObjectUnlessString = true; // or array, but that's checked by default. we need to prevent a string indexed as array to be marked as object
				else this.setTypeToRef(lastFound, 'Object');
			}
			lastFound = null;

			// unset these properties because they are clearly not the last in the chain
			delete token.assignTargetIsDynamic;
			delete token.assignTargetToken;
			delete token.calledTargetToken;
			delete token.calledTargetParen;

			if (next.value == '['){
				token.valueType = 'dynamic';
				if (last) {
					// check if this property might be an array
					var exprType = this.getType(next.expressionArg);
					if (exprType == 'number' && (!last.trackingObject.varType || last.trackingObject.varType.indexOf('string') < 0 || last.trackingObject.varType.indexOf('Array') >= 0)) {
						// this is acceptably an array...? there is a number property access and its not a string or already an array
						last.isArrayUnlessString = true; // set to array later...
					// not all variables have been resolved at this point, maybe try again later...
					} else if (!last.tracki) last.isArrayIfExprIsNumberAndTokenNotString = next.expressionArg;
					// remember info to determine array type later
					token.assignTargetIsDynamic = true;
					token.assignTargetToken = last;
				}
				token.isValidAssigneeAfterCall = true;

				next = next.twin;
			} else if (next.value == '(') {
				token.valueType = 'call';
				// remember to parameter typing and return type and stuff. later. (tofix)
				token.calledTargetToken = last;
				token.calledTargetParen = next;

				if (last) last.tokenIsCalled = true;

				next = next.twin;
			} else if (next.value == '.') {
				token.valueType = 'dot';
				next = this.btree[next.tokposb+1];
				if (next && next.name == 2/*identifier*/) lastFound = next;
				token.isValidAssigneeAfterCall = true;
			} else {
				token.valueType = '?';
				// TOFIX: why? what could this be? error?
				token.isValidAssigneeAfterCall = true;
			}
			next = next && this.btree[next.tokposb+1];
		}

		if (next && (next.value == '++' || next.value == '--')) token.valueType = '+-';

		if (lastFound) token.lastPropertyNameWithoutSolidChainToLead = lastFound;
	},
	handlePhase: function(token, stack, _phase){
		// group the var decl, func decl and directives at the top (in that order)
		if (stack.root || stack.isFunction) {
			// perfect reason to abuse switch! ;)
			switch (_phase) {
				case this.PHASE_DIRECTIVE:
					if (token[0].expressionCount == 1) {
						var nextToken = this.btree[token.nextBlack];
						var nextNextToken = this.btree[token.nextBlack+1];
						if (nextToken.isString && nextNextToken && (nextNextToken.value == ';' || nextNextToken.name == 13/*asi*/)) {
							nextToken.isDirective = true;
							break;
						}
					}
					// move to next phase if we reach this point
					_phase = this.PHASE_VAR;
					// fallthrough
				case this.PHASE_VAR:
					if (token[0].sub == 'var') {
						// only one var statement allowed
						_phase = this.PHASE_FUNC;
						break;
					}
					// move to next phase if we reach this point
					_phase = this.PHASE_FUNC;
					// fallthrough
				case this.PHASE_FUNC:
					if (token.isFunction) {
						break;
					}
					// move to next phase if we reach this point
					_phase = this.PHASE_REST;
					// fallthrough
				case this.PHASE_REST:
					// if var or function encountered here, mark them as oops
					if (token[0].sub == 'var') {
						this.addWarning(this.btree[token.nextBlack], 'group vars');
					} else  if (token.isFunction) {
						this.addWarning(this.btree[token.nextBlack], 'func decl at top');
					}
			}
		}
		return _phase;
	},
	enteringNewClause: function(stack, parent, lastClause, index){
		if (stack.sub == 'case') ++parent.cases;
		else parent.hasDefault = true;

		// if the lastClause was not cleared, no flow-stop statement was encountered. mark the clause as such.
		if (lastClause) {
			lastClause.stops = 0;
			parent.maxStops = 0; // weakest link
			lastClause.shouldBreak = true; // see phase 2
		}

		// move to next clause
		lastClause = stack;
		lastClause.hasStatements = 0;
		// if parent[index] is the header, the next item must be a body, but it may also be absents
		if (parent[index+1] && (parent[index+1].desc != 'switch clause body' || parent[index+1].length == 0)) this.addWarning(this.btree[stack.nextBlack], 'empty clause');
		
		return lastClause;
	},
	determineFlowBreakers: function(cstack, pstack, lastClause){
		// this method is called on the way back down a stack. that allows you to know the flow breaker status for all children statements.

		// if the stack is already marked as flow breaking, this statement will come after a flow-breaker and is therefor unreachable
		// if the parent was dead on arrival, the children are, regardless of comments below
		// never mark catch and finally as dead, regardless of comments below
		// try without catch can cause death
		// when try has catch, neither try nor catch cause death on their own, but can together
		// finally runs regardless so cannot be dead and can cause death
		if ((pstack.flowOnArrival || (pstack.stops && !pstack.hasCatch && pstack.sub != 'catch' && pstack.sub != 'finally')) && !cstack.isFunction) {
			// stack.sub != 'object literal' // TOFIX: why was this checked too?
			var token = this.btree[cstack.nextBlack];
			if (token && token.statementStart && !token.unreachableCode) {
				token.unreachableCode = true;
				token.deadCode = true;
			}
		}

//		console.log('down', ['parent', pstack.sub, pstack.stops, pstack, pstack.flowOnArrival, 'child', cstack.sub, cstack.stops, cstack]);

		// we decide on flow breaking here. if it's already decided, then skip it
		// some "catches" (haha, funny joke is funny, i mean caveats):
		// - a no flow breaks through the function barrier. throw technically does, but only in the actual call. so not even throw can break through functions here.
		// - if the parent stack was dead by cause of previous code, it and its children will have already been flagged dead
		// - if the try was dead code before entering, none of the below applies and everything is dead regardless.
		// - try with catch does not cause dead code. any throw will be caught and we cant "prove" either way.
		// -- an exception would be silly stuff like an immediate "bare" return statement. zeon wont check for this very edgy case.
		// -- if the try does not deal with variables. zeon does not check for this silly case.
		// - catch is never dead code and only causes it if the try would cause it; still flimsy at best, but ok i guess
		// - finally is never dead code but certainly causes it
		if (!pstack.stops && !cstack.isFunction) {
			if (cstack.sub == 'try') {
				// this is where we decide whether the parent statement is rendered dead due to the try(-catch-finally) statement

				// a try only causes the parent to be dead code when
				// - the try broke and has no catch clause
				// - the try broke and so did its catch clause
				// - the finally broke, regardless
				if ((cstack.tryBrokeFlow && (!cstack.hasCatch || cstack.catchBrokeFlow)) || cstack.finallyBrokeFlow) {
					pstack.stops = Math.max(cstack.tryBrokeFlow|0, cstack.catchBrokeFlow|0, cstack.finallyBrokeFlow|0);
				}
			} else if (cstack.sub == 'tryblock') {
				pstack.tryBrokeFlow = cstack.stops;
			} else if (cstack.sub == 'catch') {
				pstack.catchBrokeFlow = cstack.stops;
			} else if (cstack.sub == 'finally') {
				pstack.finallyBrokeFlow = cstack.stops;
			} else if (cstack.sub == 'for') { // loops might not be entered at all
			} else if (cstack.sub == 'while') { // loops might not be entered at all
			} else if (cstack.sub == 'do') { // loops might not be entered at all
			} else {

				// determine whether branch broke the flow
				if (pstack.sub == 'if') {
					// for the if, we need to track branches. kind of annoying, but whatever
					if (cstack.desc == 'statement-parent') {
						// if statement
						pstack.ifStops = cstack.stops;
					} else if (cstack.sub == 'else') {
						pstack.elseStops = cstack.stops;
					}
				} else {
					// we found a break, return, continue or throw in the if
					if (cstack.sub == 'if') {
						// if at least one branch doesnt stop, the if doesnt stop
						if (!cstack.ifStops || !cstack.hasElse || !cstack.elseStops) pstack.stops = 0;
						// if the stops are equal, just assign it
						else if (cstack.ifStops == cstack.elseStops) cstack.stops = cstack.ifStops;
						// if either branch is a throw, take the other (weaker) branch
						else if (cstack.ifStops == this.IS_THROW) cstack.stops = cstack.elseStops;
						else if (cstack.elseStops == this.IS_THROW) cstack.stops = cstack.ifStops;
						// if either branch is a return, take the other (weaker) branch
						else if (cstack.ifStops == this.IS_RETURN) cstack.stops = cstack.elseStops;
						else if (cstack.elseStops == this.IS_RETURN) cstack.stops = cstack.ifStops;
						// if either branch is a labeled break, take the other (weaker) branch
						else if (cstack.ifStops == this.IS_LABELED_BREAK) cstack.stops = cstack.elseStops;
						else if (cstack.elseStops == this.IS_LABELED_BREAK) cstack.stops = cstack.ifStops;
						// the if is an unlabeled break and else a continue xor other way round.
						// the continue and unlabeled break are equally strong, just go with the weakest link: the continue.
						else cstack.stops = this.IS_CONTINUE;
					}

					// stack is before, token is after
					// if stops == IS_BREAK | IS_CONTINUE, stop after switch, for, while, do, label
					// if stops == IS_LABELED_BREAK, stop before function
					// if stops == IS_FUNCTION, stop after function
					// if stops == IS_THROW, stop at try AND at function, because the next code has nothing to do with it unless the function is invoked immediately
					// for try/catch, this is not fool proof because there could be just a finally, but we ignore that for now

					// TOFIX: IS_LABELED_BREAK can "break" through the iteration/switch barrier (but not function)
					// TOFIX: a labeled break actually only stops at the exact label (or function, which would be an error)


					if (!(
						((cstack.stops == this.IS_BREAK || cstack.stops == this.IS_CONTINUE) && (cstack.isIteration|| cstack.sub == 'switch')) ||
						(cstack.stops == this.IS_LABELED_BREAK && (cstack.isFunction || cstack.sub == 'labeled')) ||
						(cstack.stops == this.IS_RETURN && cstack.isFunction) ||
						(cstack.stops == this.IS_THROW && (cstack.sub == 'try' || cstack.isFunction))
					)) {
						//console.log(['token',token, token.value, token.desc, token.sub])
						// dont assign to switches, the clause bodies are collected and assigned using lastClause below
						if (pstack.sub != 'switch') {
							pstack.stops = cstack.stops;
						}

						// if we found a flow-stopper, reflect this in the clause header
						if (lastClause && cstack.stops) {
							lastClause.stops = cstack.stops;
							// track which flow stopper we can rely on across all clauses
							if (pstack.maxStops == null || pstack.maxStops > cstack.stops) pstack.maxStops = cstack.stops;
							lastClause = null;
						}
					}
				}
			}
		}

		return lastClause;
	},
	handleSwitchFlowBreakers: function(stack, lastClause){
		// determine switch flow stops
		// if the lastClause was not cleared, no flow-stop statement was encountered. mark the clause as such.
		if (lastClause) {
			lastClause.stops = 0;
			stack.maxStops = 0; // weakest link
			lastClause.shouldBreak = true; // see phase 2
		}
		// if maxStops is not null, at least one clause was found (very likely ;), set stops to the weakest flow-stop of all clauses
		if (stack.sub == 'switch') {
			var switchKeyword = this.btree[stack.nextBlack];
			if (stack.maxStops != null) {
				stack.stops = stack.maxStops;
				if (stack.cases == 0) this.addWarning(switchKeyword, 'quasi empty switch');
				// TOFIX: maybe also check if case is empty, to throw epsilon
				else if (stack.cases == 1) this.addWarning(switchKeyword, 'switch is an if');
			} 
			// if all cases are empty, the switch is effectively empty
			else if (!stack.cases && !stack.hasDefault) this.addWarning(switchKeyword, 'empty switch');
			else if (!stack.cases && stack.hasDefault) this.addWarning(switchKeyword, 'quasi empty switch');
		}
	},
	processIdentifier: function(token, _scope){
		if (token.meta == 'var name' || token.meta == 'func decl name' || token.meta == 'func expr name' || token.meta == 'parameter') token.varNameDecl = true;
		if ((token.varNameDecl || token.leadValue) && (!ZeParser.regexLiteralKeywords.test(token.value) || token.value == 'this')) {
			if (token.varNameDecl) this.processVarCreate(token, _scope);
			else this.processLeadValue(token, _scope);

			// at this point, there ought to be a tracking object on the variable...
			if (!token.trackingObject) {
				if (this.hasError) token.trackingObject = {}; // happens for: `function f(){ try { } catch(e){ function g`
				else throw "wtf no tracking object?";
			}
			// code below depends on isEcma and isBrowser
			if (!token.trackingObject.checkedKeywords) this.checkKnownKeywords(token);

			if (token.meta == 'parameter') token.trackingObject.isFuncParameter = true;

			this.getJspath(token, true);

			// detect globals
			if (token.trackingObject.implicit) {
				if (token.trackingObject.isEcma || token.trackingObject.isBrowser) {
					this.addWarning(token, 'known implicit global');
					this.collects.knownGlobals.push(token);
				} else {
					this.addWarning(token, 'unknown implicit global');
					this.collects.implicitGlobals.push(token);
				}
			}

			// if this token was used with new, it is marked as being a constructor. 
			// log it in the tracking object and apply this knowledge if a function is ever assigned
			if (token.isConstructor) {
				token.trackingObject.isConstructor = true;
				token.trackingObject.constructorName = token.constructorName;
			}

			// this if is used by function declarations that are constructors. allows us to change a func to constructor when used with new
			if (token.functionStack) {
				token.trackingObject.functionStack = token.functionStack;
			}

			// for func decls later used with new (in fact, this instance of the func was used with new)
			if (token.trackingObject.functionStack && token.isConstructor) {
				token.trackingObject.functionStack[0].isConstructor = true;
				token.trackingObject.functionStack[0].constructorName = token.constructorName;
			}


		}

		if (token.value == 'this') {
			this.fixRefsTothis(_scope, token);
		}

		if (token.value == 'function' || token.value == '{' || token.value == '[') {
			this.getJspath(token, true);
		}
	},
	fixRefsTothis: function(scope, target){
		// only called for `this` right now
		for (var i=0; i<scope.length; ++i) {
			var trackingObject = scope[i];
			// the parser will have added this variable to the current scope level if it was declared here
			// so we dont need to check for arrays, the var will exist on this level.
			if (!(trackingObject instanceof Array)) {
				if (trackingObject.value == target.value) {
					// create reference to this "shared" object with some variable information
					target.trackingObject = trackingObject;
					// create reference on scope object. this way we can easily find all refs to this var.
					if (!trackingObject.refs) trackingObject.refs = [];

					trackingObject.refs.push(target);

					return;
				}
			}
		}
		throw "Post processing error. Every scope should contain `this`.";
	},

	/**
	 * In phase two we can assume that all lead variables have a tracking object. All
	 * jsdocs have been attached to at least their next function/var decl. Line stuff
	 * has been done. In phase two we can do stuff that requires at least some of the
	 * work done in phase 1.
	 * - switch case/default flow checks
	 * - type checking (now that everything has a scope object)
	 * - property checking
	 * - check for Block after statement headers
	 * 
	 * @param {Object[]} stack
	 */
	phase2: function(stack, _labels, _insideConditional, _insideFunction, _insideIteration, _insideSwitch){
		// get labels
		if (stack.labels) {
			// entering new statement / function
			_labels = stack.labels;
		}

		// do various semantic environment state administration
		if (stack.isIteration) _insideIteration = true;
		else if (stack.sub == 'switch') _insideSwitch = true;
		// when entering a function, we clear the semantic environment when going deeper
		else if (stack.isFunction) {
			_insideConditional = false;
			_insideIteration = false;
			_insideFunction = stack;
		}

		var seenIn = false;
		var deleteToken = null;
		stack.forEach(function(token, i){
			if (stack.sub == 'block' && token.sub == 'block') this.addWarning(this.btree[stack.nextBlack], 'double block');
			else if ((stack.root || stack.desc == 'func body' || stack.sub == 'block') && token.sub == 'block') this.addWarning(this.btree[token.nextBlack], 'useless block');
			
			// statement headers are not wrapped in own group (maybe they ought to be..)
			if (token.statementHeaderStart && stack.forType != 'each') _insideConditional = true;
			else if (token.statementHeaderStop) _insideConditional = false;
			// for header is only the second part
			else if (token.forEachHeaderStart) _insideConditional = true;
			else if (token.forEachHeaderStop) _insideConditional = false;

			if (token instanceof Array) {
				if (!seenIn && stack.forType == 'in') {
					// we track the in keyword to be able to tell the difference between the left and right side of lhs (to extract the variable name if not declaration, to make it a string...)
					// we also try to mark that token as having something assigned to it (prevents uninitialized usage warning)
					this.checkForInWrap(token, stack);
					this.checkForInVar(token);
				}

				// if a var decl has a jsdoc attached, give a ref to the first var in case this is a function
				if (stack.lastJsdoc && stack.desc == 'var decl' && token.desc == 'single var decl') {
					token.lastJsdoc = stack.lastJsdoc;
				}

				this.phase2Stack(token, stack, _labels, _insideConditional, _insideFunction, _insideIteration, _insideSwitch);

				this.checkTypeof(token, i, stack);
				this.checkStaticExpressions(token);
			} else {
				if (token.forFor) seenIn = true;

				this.phase2Token(token, stack, i, _labels, _insideConditional, _insideFunction, _insideIteration, _insideSwitch);

				// analyze delete operand
				if (token.value == 'delete') {
					// twice? i dont think so
					if (deleteToken != null) {
						this.addWarning(token, 'silly delete construct');
					} else {
						if (token) {
							var next = this.btree[token.tokposb+1];
							if (next.value == '(') {
								this.addWarning(token, 'delete not a function');
							} else if (next.name != 2/*identifier*/ || !next.leadValue) {
								this.addWarning(token, 'weird delete operand');
							} else if (next.leadValueTarget === true && (!next.trackingObject || !next.trackingObject.isDeclared) && next.targetScope && next.targetScope.global) {
								this.addWarning(token, 'weird delete operand');
							}
						}
						deleteToken = token;
					}
				}
			}
		}, this);
	},
	phase2Stack: function(stack, parent, _labels, _insideConditional, _insideFunction, _insideIteration, _insideSwitch){
		var tree = this.btree;

		// if a function does not return prematurely, it (also) returns undefined
		if (stack.isFunction && stack.stops == 0) {
			if (tree[stack.nextBlack] && tree[stack.nextBlack].value == 'function') {
				var vartype = tree[stack.nextBlack].varType;
				if (!vartype) vartype = tree[stack.nextBlack].varType = [];
				// if a function does not always explicitly return/throw, it can also return undefined.
				if (vartype.indexOf('undefined') < 0) vartype.push('undefined');
			}
		}

		// check if this group has more than one expression (ie. at least one comma)
		// its a rare edge case, but it fixes a problem for assignment: (a)=5 is ok (but (a,a)=5 is not)
		// we also want to warn against wrapped single expressions. i think. but maybe not.
		if (stack.desc == 'grouped' && stack.numberOfExpressions == 1) this.addWarning(tree[stack.nextBlack], 'useless parens');

		// break or default without a break, return, continue or throw.
		if (stack.shouldBreak) this.addWarning(tree[stack.nextBlack], 'clause should break');

		// check for comma expression as throw argument
		if (parent.sub == 'throw' && stack.desc == 'expressions' && stack.expressionCount != 1) this.addWarning(tree[parent.nextBlack], 'useless multiple throw args');

		// check whether the statement starts with new
		if (stack.desc == 'statement' && tree[stack.nextBlack] && tree[stack.nextBlack].value == 'new') this.addWarning(tree[stack.nextBlack], 'new statement');

		// if a jsdoc is queued, try to attach it to the current token
		if (stack.lastJsdoc) this.processJsdoc(stack);

		if (this.config.warnings && stack.desc == 'grouped') {
			// check for wrapped function expr: (function(){})
			// get first child. should be (. From there, find next token, should be `function`
			var paren = tree[stack.nextBlack];
			if (paren && paren.value == '(') {
				var func = tree[paren.tokposb+1];
				if (func && func.value == 'function') {
					var rhc = func.rhc;
					if (rhc && paren.twin && tree[rhc.tokposb+1] == paren.twin) {
						this.addWarning(paren, 'function wrapped');
					}
				}
			}
		}

		// flag the rhs of instanceof as a constructor (and, of course, a Function type)
		// this is not an absolute truth, but in very most cases that'll be the case
		if (stack.desc == 'expression' && stack.sub == 'instanceof') {
			var targetConstructor = this.getVarRef(stack[2]);
			if (targetConstructor && targetConstructor.trackingObject) {
				var tcto = targetConstructor.trackingObject;
				if (!tcto.varType) tcto.varType = [];
				if (tcto.varType.indexOf('Function') < 0) tcto.varType.push('Function');
				tcto.isConstructor = true;
				tcto.constructorName = targetConstructor.value;
			}
		}

		this.phase2(stack, _labels, _insideConditional, _insideFunction, _insideIteration, _insideSwitch);

		// process assignments now, after running phase2, so all properties are tracked
		if (stack.desc == 'expression' && ZeParser.regexAssignments.test(stack.sub)) this.processAssignment(stack);
		// var declarations
		else if (stack.desc == 'single var decl' && stack.length > 2) this.processInitialiser(stack);
		// object literal that's (probably) not assigned to any variable. could be function arg or array content or just a stupid construction.
		else if (stack.hasObjectLiteral && !stack.processedForTypes) this.processObjectLiteralForTypes(stack);

		// i really hope at least somebody tries to break it this way. at least all my efforts wont be in vain :(
		if (stack.validatePrefixOperator || stack.validatePostOperator) this.handlePrePostFixOperatorEdgeCase(stack);

		// determine function return types by logging all returned types (ignore dead code)
		if (_insideFunction && stack.sub == 'return' && !stack[0].unreachableCode && !stack[0].deadCode) {
			var n = 0;
			while (stack[++n] && stack[n].isWhite);
			// now, either the next token is a semi or ASI, or the next token is an expressions.

			if (stack[n].desc == 'expressions') {
				var returnTypes = this.getType(stack[n]);

				var funcKeyword = _insideFunction[0];
				var vartype = funcKeyword.varType;
				// make sure the vartype array exists on the function keyword
				if (!vartype) vartype = funcKeyword.varType = [];

				if (typeof returnTypes == 'string') if (vartype.indexOf(returnTypes) < 0) vartype.push(returnTypes);
				else if (returnTypes.value == 'new') {
					var cname = returnTypes.targetExpression && returnTypes.targetExpression.constructorName;
					if (cname) if (vartype.indexOf(cname) < 0) vartype.push(cname);
					else if (vartype.indexOf('new-object-instance(todo)') < 0) vartype.push('new-object-instance(todo)');
				}
				else if (returnTypes.isObjectLiteralStart) if (vartype.indexOf('Object') < 0) vartype.push('Object');
				else if (returnTypes.isArrayLiteralStart) if (vartype.indexOf('Array') < 0) vartype.push('Array');
				else if (returnTypes.isFunction) if (vartype.indexOf('Function') < 0) vartype.push('Function');
				else if (returnTypes instanceof Array){
					returnTypes.forEach(function(o){
						if (typeof o == 'string') if (vartype.indexOf(o) < 0) vartype.push(o);
					});
				}
			}
		}

		// determine function throw types by logging all throws
		if (_insideFunction && stack.sub == 'throw' && !stack[0].unreachableCode && !stack[0].deadCode) {
			var n = 0;
			while (stack[++n] && stack[n].isWhite);
			// the next token is an expressions

			var funcKeyword = _insideFunction[0];
			var throws = funcKeyword.throws;
			// make sure the throws array exists on the function keyword
			if (!throws) throws = funcKeyword.throws = [];
			// assign the throw type
			var throwTypes = this.getType(stack[n]);
			//console.log("fixme, getType can return objects for object,array,function and new");

			if (typeof throwTypes == 'string') if (throws.indexOf(throwTypes) < 0) throws.push(throwTypes);
			else if (throwTypes.value == 'new') {
				var cname = throwTypes.targetExpression && throwTypes.targetExpression.constructorName;
				if (cname) if (throws.indexOf(cname) < 0) throws.push(cname);
				else if (throws.indexOf('new-object-instance(todo)') < 0) throws.push('new-object-instance(todo)');
			}
			else if (throwTypes.isObjectLiteralStart) if (throws.indexOf('Object') < 0) throws.push('Object');
			else if (throwTypes.isArrayLiteralStart) if (throws.indexOf('Array') < 0) throws.push('Array');
			else if (throwTypes.isFunction) if (throws.indexOf('Function') < 0) throws.push('Function');
			else if (throwTypes instanceof Array){
				throwTypes.forEach(function(o){
					if (typeof o == 'string' && throws.indexOf(o) < 0) throws.push(o);
				});
			}
		}

		// too heavy
		//if (stack.desc == 'expressions') var t = this.getTypeExpressions(stack);
	},
	phase2Token: function(token, stack, index, _labels, _insideConditional, _insideFunction, _insideIteration, _insideSwitch){

		if (token.isArrayIfExprIsNumberAndTokenNotString) {
			var exprType = this.getType(token.isArrayIfExprIsNumberAndTokenNotString);
			if (exprType == 'number' && (!token.trackingObject.varType || token.trackingObject.varType.indexOf('string') < 0 || token.trackingObject.varType.indexOf('Array') >= 0)) {
				// this is acceptably an array...? there is a number property access and its not a string or already an array
				token.isArrayUnlessString = true; // set to array later...
			}
		}

		// process unary prefix operators after processing this stack
		if (token.value == '++' || token.value == '--') {
			if (token.isUnaryOp) stack.validatePrefixOperator = true;
			// only do postfix on stupid stuff like (X)++
			else if (!token.hasBeenAssigned) stack.validatePostOperator = true;
		}

		if (token.value == 'new' && token.targetExpression) this.checkNewArgs(token);

		if (token.value == 'typeof') stack.hasTypeof = true;

		// if you declare a function param or catch scope param with single underscore, no error is given, its assuming you want that to be empty
		if (token.varNameDecl && token.trackingObject && !token.trackingObject.used && ((token.meta != 'parameter' && !token.isCatchVar) || token.value != '_')) { // since it was declared, it should exist in the scope.
			token.unused = true;
		}

		if (token.name == 7/*COMMENT_SINGLE*/) this.checkPragmasBefore(token);

		if (token.name == 10/*lineterminator*/) {
			// mark all whitespace sequentially before it as trailing whitespace
			var pos = token.tokposw;
			while (pos--) {
				if (this.wtree[pos].name == 9/*whitespace*/) this.wtree[pos].isTrailingWhitespace = true;
				else if (this.wtree[pos].name != 13/*asi*/) break; // asis are also kind of whitespace, at least in the source
			}
		}

		// dead statements...
		if ((stack.desc == 'statement' || stack.desc == 'statement-parent') && stack.numberOfExpressions == 1) {
			var prim = this.btree[stack.nextBlack];
			if (prim.isPrimitive && !prim.isDirective) {
				var next = this.btree[stack.nextBlack+1];
				if (next && (next.name == 13/*asi*/ || next.value == ';')) {
					prim.deadCode = true;
				}
			}
		}

		if (token.isPropertyOf && token.assignedPropExpression) {
			var func = this.btree[token.assignedPropExpression.nextBlack];
			if (func && (func.isFuncExprKeyword || func.isFuncDeclKeyword)) {
				token.functionStack = func.functionStack;
				if (!token.trackingObject) token.trackingObject = {refs:[token]};
				token.trackingObject.functionStack = func.functionStack;
				if (func.jsdoc) token.jsdoc = func.jsdoc;
			}
		}

		// mark this property as "declared" when it's explicitly set as a property of a prototype property (do before trackThis is called)
		// (do in phase two because props might be explicitly declared later than their usage, which would be ok, i guess)
		if (token.isPropertyOf && token.isPropertyOf.isPropertyOf && token.isPropertyOf.value == 'prototype' && token.trackingObject) token.trackingObject.isDeclaredOnProto = true;
	},

	handlePrePostFixOperatorEdgeCase: function(stack){
		// scan for ++ and validate its operand
		// stack should be a sub-expression
		// this is not fast, but its an edge case so i dont care much
		var found = false;
		var result = false;
		var last = null;
		for (var i=0; i<stack.length; ++i) {
			if (stack[i].value == '++' || stack[i].value == '--') {
				if (!last) result = false; // can happen with serious errors
				else if (stack.validatePostOperator) {
					// this is why we save the last non-whitespace
					result = this.getValidAssigneeLead(last);
					break;
				} else {
					found = stack[i];
				}
			} else if (found && !stack[i].isWhite) {
				if (stack[i].isUnaryOp) result = false;
				else if (!stack[i].isWhite) result = this.getValidAssigneeLead(stack[i]);
				break;
			}

			// only save interesting stuff
			if (!stack[i].isWhite) last = stack[i];
		}

		// either set type or warn for (serious..) problem
		if (result && result.leadValueTarget && result.leadValueTarget !== true) {
			result = result.leadValueTarget;
			this.setTypeToRef(result, 'number');
		} else if (result && result.leadValueTarget) {
			this.setTypeToRef(result, 'number');
		} else if (!result || result.leadValueTarget !== false) {
			found.badPrefixOperand = true;
		}
	},
	getVarRef: function(stack){
		//console.log(['getVarRef',stack])
		// TOFIX: this method needs refinement... I don't think it's very complete yet
		if (stack.desc == 'grouped') {
			var exp = this.getExpressionsFromGroup(stack);
			if (!exp) return false;
			return this.getVarRef(exp);
		}
		else if (stack.desc == 'expressions') return this.getVarRef(stack[stack.length-1]);
		else if (stack.desc == 'expression' && stack.length == 1) return this.getVarRef(stack[0]);
		else if (stack.desc == 'sub-expression') {
			if (stack[0].leadValue) {
				if (typeof stack[0].leadValueTarget != 'boolean') return stack[0].leadValueTarget;
				if (stack[0].leadValueTarget) return stack[0];
			}
			return false;
		}
		else if (stack.leadValue) return stack;
		return false;
	},
	getValidAssigneeLead: function(stack){
		if (!stack) {
			if (!this.hasError) console.log('getValidAssigneeLead did not receive stack', [this.lastInput]);
			return;
		}
		// either stack is a lead value and we should check where it ends up
		// or stack is a group, and we must check if it the group has exactly one expression
		if (stack.desc == 'grouped') {
			var exp = this.getExpressionsFromGroup(stack);
			if (!exp) return false;
			return this.getValidAssigneeLead(exp);
		}

		if (stack.desc == 'expressions') return this.isValidAssigneeExpressions(stack);

		if (stack.leadValue) return stack;

		if (stack.isArrayLiteralStart) return stack;
		if (stack.isObjectLiteralStart) return stack;

		return false;
	},
	isValidAssigneeExpressions: function(stack){
		if (stack.expressionCount != 1) return false;
		return this.isValidAssigneeExpression(stack[0]);
	},
	isValidAssigneeExpression: function(stack){
		if (stack.length != 1) return false;
		return this.isValidAssigneeSubExpression(stack[0]);
	},
	isValidAssigneeSubExpression: function(stack){
		if (!stack.filter(function(o){ return !o.isWhite; }).length) return false;
		if (stack[0].leadValue || stack[0].desc == 'grouped') return this.getValidAssigneeLead(stack[0]);
		return false;
	},
	getExpressionsFromGroup: function(stack){
		if (stack.numberOfExpressions != 1) return false;
		for (var i=0; i<stack.length; ++i) {
			if (stack[i].desc == 'expressions') return stack[i];
		}
		return false;
	},
	processJsdoc: function(stack){
		// http://code.google.com/p/jsdoc-toolkit/wiki/TagParam
		// http://code.google.com/intl/nl/closure/compiler/docs/js-for-compiler.html#types
		var comment = stack.lastJsdoc;
		if (!comment) return;

		this.scanForJsdoc(comment); // tofix move
		var jsdoc = comment.jsdoc;

		// ok we will first try to process the doc looking for @var's
		// if we cannot find them, assume a function doc and parse it like that
		// this is because a jsdoc to a var decl can be ambiguous; function or not?
		var hasVars = false;
		if (stack.desc == 'var decl') {
			// get jsdoc @vars
			var $vars = jsdoc.filter(function(o){ return o instanceof Array && o[3] == 'var' && o[6] && o[8]; });
			hasVars = $vars.length;
			if (hasVars) {
				// get all params of this functions (if any)
				var vars = stack.filter(function(o){ return !!(o.desc == 'single var decl'); });
				if (vars.length) {
					// jsdoc vars
					$vars.some(function(r){
						// fix name and type, remove jsdoc wrapper syntax and check if they might be switched
						var scrubbed = this.scrubJsdocNameType(r[8], r[6]);
						// returns an array with the scrubbed values
						var name = scrubbed[0];
						var type = scrubbed[1];

						// try to match it to every var decl name
						vars.some(function(v){
							// variable declarations are groups of 'single var decl', they always have 
							// a sub-expresion with at least one kid (the actual varname). we take that
							var varMatch = v[0][0];

							// check if name matches with var name
							if (varMatch && varMatch.value == name) {
								// yes, we have a match. hook type(s)
								this.setTypeToRef(varMatch, type);

								// this way we can later point out that the jsdoc type annotation was incorrect or incomplete
								varMatch.trackingObject.jsdocType = type;
								varMatch.trackingObject.jsdocPos = r.relStartPos; // this is where we got our original jsdoc information
								var desc = r[10];
								var check = this.regexJsDocScrub.exec(desc);
								if (check && check[1]) desc = check[1];
								varMatch.trackingObject.jsdocVarDesc = desc;

								return true;
							}
						}, this);
					}, this);
				}
			}
		}
		// TOFIX: hook it up to object methods
		else if (stack.isFunction) {
			// get jsdoc @params
			var $params = jsdoc.filter(function(o){
				var isArr = (o instanceof Array);
				return isArr && o[3] == 'param' && o[6];
			});
			if ($params.length) {
				// just jsdoc params
				$params.some(function(r){
					// get all params of this functions (if any)
					var params = stack.filter(function(o){ return !!(o.meta == 'parameter'); });
					if (params.length) {
						// fix name and type, remove jsdoc wrapper syntax and check if they might be switched
						var scrubbed = this.scrubJsdocNameType(r[8], r[6]);
						// returns an array with the scrubbed values
						var name = scrubbed[0];
						var type = scrubbed[1];
						// ok, name should be a string and type should be an array with the possible types (usually one) as string.

						if (name && type && type.length) {
							params.some(function(p){
								if (p.value == name) {
									// yes, we have a match. hook type(s)
									this.setTypeToRef(p, type);

									// this way we can later point out that the jsdoc type annotation was incorrect or incomplete
									p.trackingObject.jsdocType = type;
									p.trackingObject.jsdoc = comment; // jsdoc source token
									p.trackingObject.jsdocPos = r.relStartPos; // this is where we got our original jsdoc information
									p.trackingObject.relLineId = r.relLineId; // 'th line in jsdoc
									p.trackingObject.jsdocOriginalLine = r.jsdocOriginalLine;

									return true;
								}
							},this);
						}
					}
				}, this);
			}
		} else if (stack.desc != 'single var decl') {
			// var decls are taken care of at the assignment phase. anything else should probably not get a jsdoc...
			throw "Wtf";
		}
	},
	scanForJsdoc: function(comment){
		// jsdoc processing
		// try to apply the regex to each line of the comment
		// lines that dont match are saved unchanged
		// all other lines are saved as regex match
		var commentLines = comment.value.split('\n');
		var len = 0;
		var line = 0;
		comment.jsdoc = commentLines.map(function(commentLine, i){
			// [*] @(name)[ ((param)[ (param)[ (rest)]]])
			var jsdocParam = this.regexJsDoc.exec(commentLine);
			len += commentLine.length;
			++line;
			if (jsdocParam) {
				jsdocParam.relStartPos = len-commentLine.length;
				jsdocParam.relLineId = line-1; // 'th line in jsdoc
				/* (moved)
				console.log(jsdocParam);
				if (jsdocParam[3] == 'param') {
					// record in scope
					console.log("logging",jsdocParam[6],"as",jsdocParam[8])
					_scope.push({
						value: jsdocParam[6],
						varType: [jsdocParam[8]],
						jsdoc: true
					})
				}
				*/
				jsdocParam.jsdocOriginalLine = commentLine; // required to get orientation later...
				return jsdocParam;
			}

			return commentLine;
		},this);
	},
	scrubJsdocNameType: function(name, type){
		// if only `@param x`, only the name is found
		if (!name) {
			name = type;
			type = [];
		// validate type
		} else if (type[0] == '{' && type[type.length-1] == '}') {
			type = type.substring(1, type.length-1);
			// type may be multiples
			type = type.split('|');
			// type may be suffixed by [] to indicate an array of them.
			// in that case we'll simply replace it by 'Array', for now (TOFIX? :)
			type = type.map(function(o){
				if (o[o.length-2] == '[' && o[o.length-1] == ']') return 'Array';
				return o;
			},this);
		} else {
			// type is not wrapped in curlies, assume it's the name instead.
			name = type;
			type = [];
		}

		// sanatize name (optional, default)
		if (name[0] == '[' && name[name.length-1] == ']') name = name.substring(1, name.length-1);
		name = name.split('=')[0]; // TOFIX: check if type of default is in list of types and warn if problem.

		// sanatize types, for now. TOFIX: migrate to a system where constructors are used as type
		type = type.map(function(o){
			return o;
		}, this);

		return [name, type];
	},
	processAssignment: function(stack){
		// for the expression assignment, we first need to determine whether it was an assignment to a dynamic property
		// this is pretty easy though :)
		var first = this.btree[stack.nextBlack];
		if (first && first.assignTargetIsDynamic) this.processAssignmentToDynamic(first, stack);
		else this.processAssignmentToVarOrProp(stack);
	},
	processAssignmentToDynamic: function(lead, stack){
		var target = lead.assignTargetToken;
		var rhsType = this.getType(stack[2]);
		if (rhsType && target) {
			if (!target.trackingObject) target.trackingObject = {};
			if (!target.trackingObject.arrayTypes) target.trackingObject.arrayTypes = [];
			if (!(rhsType instanceof Array)) rhsType = [rhsType];
			var types = target.trackingObject.arrayTypes;
			rhsType.forEach(function(t){
				if (typeof t == 'string' && types.indexOf(t) < 0) types.push(t);
				else if (t.isArrayLiteralStart) types.push('Array');
				else if (t.isObjectLiteralStart) types.push('Object');
			},this);
		}
	},
	processAssignmentToVarOrProp: function(stack){
		var lhs = stack[0];
		lhs = this.getValidAssigneeLead(lhs[0]); // only valid is bare var, grouped var w/o comma or some kind of trailing property access
		if (!lhs || !lhs.leadValueTarget && !lhs.lastPropertyNameWithoutSolidChainToLead) {
			if (!lhs || lhs.leadValueTarget !== false && !lhs.isValidAssigneeAfterCall) {
				if (!this.hasError) console.log(["lead value dot property is itself", lhs, lhs == lhs.lastDotProperty]); // lead value has lastDotProperty which is equal to itself?
				// error example: var x = = y;
				if (stack[1] && stack[1][0]) this.addWarning(stack[1][0], 'assignment bad');
			}
			return;
		}

		if (lhs.lastPropertyNameWithoutSolidChainToLead) {
			lhs = lhs.lastPropertyNameWithoutSolidChainToLead;
			if (!lhs.trackingObject) lhs.trackingObject = {};
		} else if (lhs.leadValueTarget !== true) lhs = lhs.leadValueTarget;
		lhs.wasAssignedSomething = true; // mark this token as having something assigned to it

		var op = stack[1][0];
		if (!op) return; // can happen with errors...

		if (lhs.trackingObject && lhs.trackingObject.isFuncExprName) this.addWarning(op, 'func expr name is read-only');

		var type = false;
		if (lhs.trackingObject) lhs.hadAssignment = true; // mark variable as being initialized (one way or the other...)
//		if (op.value != '+=') {
			
			type = this.getBinaryOperatorType(op.value);
			if (type) return this.setTypeToRef(lhs, type);
//		}
		var rhs = stack[2];
		return this.assignRightToLeft(lhs, op, rhs);
	},
	processInitialiser: function(stack){
		if (stack[0] && stack[0][0] && stack[0][0].trackingObject) stack[0][0].hadAssignment = true; // mark variable as being initialized
		return this.assignRightToLeft(stack[0][0], stack[1][0], stack[2], stack.lastJsdoc);
		/*
		var lhs = stack[0][0];
		var rhs = stack[2][0];

		if (rhs.hasObjectLiteral) throw "does tihs still happen?";
		else if (rhs.desc == 'expression' && rhs[0].hasObjectLiteral) this.assignObjectLiteral(lhs, rhs[0]);
		else {
			var type = this.getType(rhs);
			if (type) {
				if (type.isFunction) {
					lhs.functionStack = type;
					lhs.trackingObject.functionStack = type;
					if (lhs.trackingObject.isConstructor) {
						type[0].isConstructor = true;
						type[0].constructorName = lhs.trackingObject.constructorName;
					}
	
					// try to find jsdoc
					// get the m-comment:
					type.lastJsdoc = stack.varStack.lastJsdoc;
					// process it now
					this.processJsdoc(type);
				}
				
				this.setTypeToRef(lhs, type);
			}
		}
		*/
	},
	/**
	 * Do the second part of assigning a value right to left. This method is used for both regular assignment
	 * and variable initialization. For regular assignment, the shortcuts have already been taken and input
	 * checked (meaning that it actually is an assignment of some kind). Initializers dont need this checking.
	 * 
	 * @param {Object} lhs assignee
	 * @param {Object} op operator token
	 * @param {Object} rhs expression being assigned
	 * @param {Object} [lastJsdoc] in case of initializers, this is the jsdoc before the var decl if there was any, used to propagate jsdocs for function expressions (annoying construct)
	 */
	assignRightToLeft: function(lhs, op, rhs, lastJsdoc){
		// now if the rhs is an object literal, we get off easy
		if (rhs.hasObjectLiteral) {
			// this is a normal assignment, unless it's not a normal assignment ;)
			if (op.value == '=') this.assignObjectLiteral(lhs, rhs);
			// this could be number, but only if toString returns a number. which is silly. and stupid. and silly. and not my problem.
			else if (op.value == '+=') this.setTypeToRef(lhs, 'string');
			// all other assignment operators try to apply some kind of math to the object, resulting in NaN (unless valueOf returns a number, but we ignore that for now)
			else this.setTypeToRef(lhs, 'number');
		} else {
			// shit gets difficult now.

			// ok, the operator is either =, += or a math assignment.
			// for the assignment, we assign the type of rhs.
			// for the +=, we have to filter the rhs AND lhs for string and number. results in either string, number or both.
			// for math assignments, the result will be a number, easypeasy

			var rhsType = this.getType(rhs);
			if (op.value == '=') {
				var type = rhsType;
				if (type) {
					// if an earlier step (phase1) detected the new operator on this var or property, annotate this function as a constructor
					// when assigning to properties, this step comes later
					if (type.isFunction) {
						// create ref to function stack for whatever use later
						lhs.functionStack = type;
						// attach jsdoc if passed on
						if (lastJsdoc) {
							lhs.jsdoc = lastJsdoc;
							lhs.functionStack.jsdoc = lastJsdoc;
							// if this is a function expression assignment, the structure should be:
							// expressions -> expression -> sub-expression -> func expr -> func keyword
							if (rhs[0] && rhs[0][0] && rhs[0][0][0] && rhs[0][0][0][0] && rhs[0][0][0][0].isFuncExprKeyword) {
								var funcKeyword = rhs[0][0][0][0];
								funcKeyword.jsdoc = lastJsdoc;
								if (funcKeyword.funcName) funcKeyword.funcName.jsdoc = lastJsdoc;
							} else if (!this.hasError) console.log("expected specific form for solo function expression", [this.lastInput]);
						}
						// if this is a regular (or global) var and used with new at some point, define function as constructor now
						// type is a func expr stack
						type.assignedToLead = lhs; // link to the variable (or non object literal properties) that received this assignment, for linking this...
						if (lhs.trackingObject) {
							lhs.trackingObject.functionStack = type;
							if (lhs.trackingObject.isConstructor) {
								var funcKeyword = type[0];
								funcKeyword.isConstructor = true;
								funcKeyword.constructorName = lhs.trackingObject.constructorName;
							}
						}
					} else if (type.isArrayLiteralStart) {
						if (lhs.trackingObject && type.trackingObject && type.trackingObject.arrayTypes) {
							// copy them
							if (!lhs.trackingObject.arrayTypes) lhs.trackingObject.arrayTypes = [];
							type.trackingObject.arrayTypes.forEach(function(t){
								if (lhs.trackingObject.arrayTypes.indexOf(t) < 0) lhs.trackingObject.arrayTypes.push(t);
							});
						}
					}


					this.setTypeToRef(lhs, type);
				}

			} else if (op.value == '+=') {
				// normalize left and right to either a string ('number' or 'string'), true (for both) or false (unknown)

				// get right type
				if (rhsType instanceof Array) {
					var hasString = false;
					var hasNumber = false;
					rhsType.forEach(function(s){
						hasString = hasString || s=='string';
						hasNumber = hasNumber || s=='number';
					});
					if (hasString && hasNumber) rhsType = true;
					else if (hasString) rhsType = 'string';
					else if (hasNumber) rhsType = 'number';
					else rhsType = false;
				}

				// get left type
				var lhsType = false;
				if (lhs.trackingObject && lhs.trackingObject.varType) {
					var hasString = false;
					var hasNumber = false;
					lhs.trackingObject.varType.forEach(function(s){
						hasString = hasString || s=='string';
						hasNumber = hasNumber || s=='number';
					});
					if (hasString && hasNumber) lhsType = true;
					else if (hasString) lhsType = 'string';
					else if (hasNumber) lhsType = 'number';
					else lhsType = false;
				}

				// if we dont know about left and right, just add both
				if (!lhsType && !rhsType) this.setTypeToRef(lhs, ['string', 'number']);
				// if we dont know about left, we just assign right. screw it
				else if (!lhsType) this.setTypeToRef(lhs, rhsType);
				// if we dont know about right, we should probably go with string...
				else if (!rhsType) this.setTypeToRef(lhs, 'string');
				// the result is number if both sides are just numbers
				else if (lhsType == 'number' && rhsType == 'number') this.setTypeToRef(lhs, 'number');
				// the result is string if either side is a string
				else if (lhsType == 'string' || rhsType == 'string') this.setTypeToRef(lhs, 'string');
				// for now, otherwise, just assign them both
				else this.setTypeToRef(lhs, ['string', 'number']);
			} else {
				this.setTypeToRef(lhs, 'number');
			}
			// take rhsType n type  (conjunction)
			// since type is at most two values, filter
		}
	},
	assignObjectLiteral: function(lhs, stack){
		if (!stack) {
			if (!this.hasError) {
				console.log("assignObjectLiteral did not recieve stack", [this.lastInput]);
			}
			return;
		}
		stack.processedForTypes = true;

		if (!stack.definedProperties) throw 'obj should have been processed';
		var defprops = stack.definedProperties;

		if (!lhs.trackingObject.properties) lhs.trackingObject.properties = {};
		var props = lhs.trackingObject.properties;

		if (lhs.value == 'prototype') lhs.trackingObject.assignedObjLit = stack[0];

		// run through all properties of the object literal
		for (var key in defprops) {
			var token = defprops[key];

			var typeExpression = token.assignedPropExpression; // expression that was assigned to this property, to get type from

			if (!typeExpression) {
				if (!this.hasError) console.log("i think this is an accessor, if not please fix me", [this.lastInput]);
				continue; // probably an accessor (TOFIX!)
			}

			token.assignedPropertyOf = lhs; // chain to master after assignment. used for determining `this` path for object literal properties

			// add to properties of lhs
			if (this.hasOwn(props, key)) props[key].refs.push(token);
			else props[key] = {value:key, refs:[token]};

			if (defprops[key].trackingObject && defprops[key].trackingObject.functionStack) props[key].functionStack = defprops[key].functionStack;

			// attach tracking object to obj property
			defprops[key].trackingObject = props[key];

			// mark prototype properties as "declared", to find undeclared properties later
			if (lhs.value == 'prototype' && lhs.isPropertyOf) {
				token.trackingObject.isDeclaredOnProto = true; // mark this property as being explicitly declared on the prototype object
			}

			// get type
			var type = this.getType(typeExpression);

			// assign to property
			if (type) this.setTypeToRef(token, type);
		}

		// mark lhs an object
		if (!stack.hasObjectLiteral || stack[0].leadValueTarget === true) {
			this.setTypeToRef(lhs, 'Object');
		}
	},
	processObjectLiteralForTypes: function(stack){
		stack.processedForTypes = true;

		if (!stack.definedProperties) throw 'obj should have been processed';
		var defprops = stack.definedProperties;

		for (var key in defprops) {
			var token = defprops[key];

			var typeExpression = token.assignedPropExpression; // expression that was assigned to this property, to get type from

			if (!typeExpression) {
				if (!this.hasError) console.log("if this is not an accessor, check me", [this.lastInput]);
				continue; // TOFIX: accessor property, i hope
			}

			// get type
			var type = this.getType(typeExpression);

			if (type instanceof Array) {
				type.assignedToObjLitProperty = token;
			}

			// assign to property
			if (type) this.setTypeToRef(token, type, true);
		}
	},
	checkStaticExpressions: function(stack){
		var tree = this.btree;
		// check for static expressions
		// expressions with two primitives are considered static (their result can never be different)...
		// even though this might not be the case for regex (lastIndex property amongst regex literals is shared...), the warning is still valid.
		if (
			stack.desc == 'expression' &&
			stack.length == 3 &&
			(
				stack[0].staticExpression ||
				(stack[0].desc == 'sub-expression' && tree[stack.nextBlack] && tree[stack[0].nextBlack].isPrimitive && (!tree[stack[0].nextBlack+1] || tree[stack[0].nextBlack+1].value != '.'))
			) &&
			(
				(stack[2].staticExpression ||
					((stack[2].desc == 'sub-expression' && tree[stack[2].nextBlack] && tree[stack[2].nextBlack].isPrimitive) && (!tree[stack[2].nextBlack+1] || tree[stack[2].nextBlack+1].value != '.'))
				) ||
				stack[1].sub == '?' // if the lhs of the ternary operator is static, the rhs is always the same (but not neccessarily static by itself...)
			)
		) {
			// dont mark the colon as static because that doesnt make sense
			if (stack[1].sub != ':') this.addWarning(stack[1][0], 'static expression');

			// only set static expression on the entire ternary expression if all three parts are static
			if (stack[1].sub != '?' || stack[2].staticExpression) stack.staticExpression = true;

			// it can be already set (somehow...)
			else stack.staticExpression = false;

			stack[1][0].staticExpression = true;
		} else if (stack.desc == 'grouped') {
			// this elaborate double loop simply searches for the last expression in the comma chained expression list in the group
			// if that expr is static, so is the group's result
			for (var i=0; i<stack.length; ++i) {
				// look for first array, this is the list of expressions
				if (stack[i] instanceof Array) {
					var j=stack[i].length;
					while (j--) if (stack[i][j] instanceof Array) {
						if (stack[i][j].staticExpression) {
							stack.staticExpression = true;
						}
						break;
					}
					break;
				}
			}
		} else if (stack.desc == 'sub-expression' && stack[0] && stack[0].staticExpression) {
			stack.staticExpression = true;
		} else {
			// whitespace might screw up length check
			var ministack = stack.filter(function(o){ return !o.isWhite; });
			//console.log('test',stack.desc, stack.length, stack.filter(function(o){ return !o.isWhite; }))
			if (ministack.length == 1 && (ministack[0].staticExpression || ministack[0].isPrimitive)) {
				stack.staticExpression = true;
			}
		}
	},
	checkTypeof: function(token, i, stack){
		if (token.hasTypeof) {
			// this subsexpression has a typeof
			// the next element should be an operator
			// and the element after that should be a string
			// those strings can only be one of a limited set

			if (stack[i+2]) {
				var testForOp = this.btree[stack[i+1].nextBlack];
				if (testForOp) {
					testForOp.typeofOperator = true;
					switch (testForOp.value) {
						case '===':
						case '!==':
							this.addWarning(testForOp, 'typeof always string');
							// fall through...
						case '==':
						case '!=':
							// only warn for rhs strings if comparing here...
							// prevent warning for == and !=
							testForOp.isTypeofOperator = true;

							var testForString = this.btree[stack[i+2].nextBlack];
							if (testForString && testForString.isString) {
								switch (testForString.value.substring(1, testForString.value.length-1)) {
									case 'boolean':
									case 'string':
									case 'number':
									case 'undefined':
									case 'object':
									case 'function':
										break;
									default:
										this.addWarning(testForString, 'unlikely typeof result');
								}
							}
							break;
						default:
							this.addWarning(testForOp, 'weird typeof op');
					}
				}
			}
		}

	},
	checkForInWrap: function(stack, parent){
		if (parent.desc == 'statement') {
			// this is the single body for a for-in loop
			// check if it's wrapped to check for own property

			// the next tokens should either be an if or a block with a single if
			// the if should start with exactly: if (obj.hasOwnProperty(key))
			// so we need to somehow grab the key and obj names too, in some weird future :) TOFIX

			// if block, make sure it contains only a single statement
			if (stack.sub == 'block' && stack.statements != 1) {
				this.addWarning(this.btree[stack.nextBlack], 'unwrapped for-in');
				return;
			}

			parent.some(function(t){
				if (t.desc == 'statement-parent') {
					// process the statement and match the desired pattern
					var tree = this.btree;
					var pos = t.nextBlack;
					if (tree[pos].value == '{') ++pos; // skip block start (if block, we already "validated" it before
					if (!(
						tree[pos].value == 'if' &&
						tree[++pos].value == '(' &&
						tree[++pos].name == 2/*identifier*/ &&
						tree[++pos].value == '.' &&
						tree[++pos].value == 'hasOwnProperty' &&
						tree[++pos].value == '(' &&
						tree[++pos].name == 2/*identifier*/ &&
						tree[++pos].value == ')'
					)) {
						// for is not wrapped
						this.addWarning(parent[0], 'unwrapped for-in');
					}
					return true; // only one
				}
			},this);			
		}
	},
	checkForInVar: function(stack){
		if (stack.desc == 'var decl') {
			var lhsTracking = stack.filter(function(o){ return o.desc == 'single var decl'; });
			if (lhsTracking && lhsTracking[0] && lhsTracking[0][0] && lhsTracking[0][0][0]) {
				var lhs = lhsTracking[0][0][0];
				lhs.hadAssignment = true; // by the for-in... used for determining uninitialized variable usage in loops.
				lhsTracking = lhs.trackingObject;
				if (lhsTracking) {
					if (!lhsTracking.varType) lhsTracking.varType = [];
					if (lhsTracking.varType.indexOf('string') < 0) lhsTracking.varType.push('string');
				}
			}
		} else if (stack.desc == 'expressions') {
			if (stack[0] && stack[0][0] && stack[0][0][0]) {
				var target = stack[0][0][0];
				target.hadAssignment = true; // by the for-in... used for determining uninitialized variable usage in loops.
				if (target && target.trackingObject) {
					target = target.trackingObject;
					if (!target.varType) target.varType = [];
					if (target.varType.indexOf('string') < 0) target.varType.push('string');
				}
			}
		}
	},
	checkNewArgs: function(token){
		var te = token.targetExpression;
		if (!te.varType) te.varType = [];
		if (te.varType.indexOf('Function') < 0) te.varType.push('Function');
		if (te.trackingObject) {
			if (!te.trackingObject.varType) te.trackingObject.varType = [];
			if (te.trackingObject.varType.indexOf('Function') < 0) te.trackingObject.varType.push('Function');
			te.trackingObject.isConstructor = true;
			te.trackingObject.constructorName = te.constructorName;
		}
	},
	checkPragmasBefore: function(token){
		var result = token.value.match(this.regexPragmas);
		if (result) {
			this.collects.pragmas.push(token);
			token.isPragma = true;
			token.pragmaName = result[1];
			token.pragmaArg = result[2];

			if (!token.pragmaArg) {
				if (token.pragmaName == 'define' || token.pragmaName == 'ifdef' || token.pragmaName == 'elseifdef' || token.pragmaName == 'inline' || token.pragmaName == 'macro') {
					this.addWarning(token, 'pragma requires name parameter');
				}
			}

			// mark all tokens on the same line as pragma line
			var upto = this.markPragmaLine(token);
			// and save the last token for the previous pragma block (if it applies, otherwise it'll be ignored anyways)
			token.pragmaPosEnd = upto;

			token.pragmaRest = result[3];
			if (!token.pragmaRest && token.pragmaName == 'macro') {
				this.addWarning(token, 'pragma requires value parameter');
			}
			if (token.pragmaName == 'define' && token.pragmaArg) {
				// TOFIX: could probably pass on a custom object
				this.collects.defines.push(token);
			}

			switch (token.pragmaName) {
				case 'ifdef':
					this.pragmas.ifdefs.push(token);
					break;
				case 'elseifdef':
					if (this.pragmas.ifdefs.length == 0) this.addWarning(token, 'missing ifdef');
					else {
						var start = this.pragmas.ifdefs[this.pragmas.ifdefs.length-1];
						token.pragmaStart = start;
						if (start.pragmaElseIf) start.pragmaElseIf.push(token);
						else start.pragmaElseIf = [token];
					}
					break;
				case 'elsedef':
					if (this.pragmas.ifdefs.length == 0) this.addWarning(token, 'missing ifdef');
					else {
						var start = this.pragmas.ifdefs[this.pragmas.ifdefs.length-1];
						token.pragmaStart = start;
						start.pragmaElse = token;
					}
					break;
				case 'endif':
					if (this.pragmas.ifdefs.length == 0) this.addWarning(token, 'missing ifdef');
					else {
						var start = this.pragmas.ifdefs.pop();
						token.pragmaStart = start;
						start.pragmaStop = token;
					}
					break;

				case 'inline':
					this.pragmas.inlines.push(token);
					break;
				case 'endline':
					if (this.pragmas.inlines.length == 0) this.addWarning(token, 'missing inline');
					else {
						var start = this.pragmas.inlines.pop();
						token.pragmaStart = start;
						start.pragmaStop = token;
						this.pragmas.inlineNames.push(start);
					}
					break;

				case 'macro':
					if (token.pragmaArg && !this.regexValidMacro.test(token.pragmaArg)) {
						// zeon can currently not really easily replace a macro name that spans multiple tokens (A.B, for instance) so we dont. maybe we can TOFIX that some day.
						this.addWarning(token, 'macro name should be identifier');
					}
					if (token.pragmaArg && token.pragmaRest) {
						token.pragmaNameParts = token.pragmaArg.split('.'); // find last property access. that's what we'll search for (first)
						token.pragmaNameLastPart = token.pragmaNameParts[token.pragmaNameParts.length-1];
						// add pragma arg to list of macro targets. the next phase will then flag any token that is targeted this way
						this.pragmas.macros.push(token);
					}
					break;
			}

		}
	},
	markPragmaLine: function(match){
		// assume match is a pragma
		// mark all tokens on the same line as being in the pragma line
		// all those tokens will be dimmed in the output
		var pos = match.tokposw-1;
		var line = match.startLineId;
		// now mark all tokens before the comment, if any
		while (pos >= 0 && this.wtree[pos].startLineId == line) {
			this.wtree[pos].inPragmaLine = true; // will be removed when scrubbing pragmas
			--pos;
		}
		// return the first token on the previous line (pretty much always a line terminator, except of course when pos=-1)
		return pos;
	},

	phase3: function(stack, _labels, _insideConditional, _insideFunction, _insideIteration, _insideSwitch){
		// get labels
		if (stack.labels) {
			// entering new statement / function
			_labels = stack.labels;
		}

		// do various semantic environment state administration
		if (stack.isIteration) _insideIteration = true;
		else if (stack.sub == 'switch') _insideSwitch = true;
		// when entering a function, we clear the semantic environment when going deeper
		else if (stack.isFunction) {
			_insideConditional = false;
			_insideIteration = false;
			_insideFunction = stack;
		}

		stack.forEach(function(token, index){
			// statement headers are not wrapped in own group (maybe they ought to be..)
			if (token.statementHeaderStart && stack.forType != 'each') _insideConditional = true;
			else if (token.statementHeaderStop) _insideConditional = false;
			// for header is only the second part
			else if (token.forEachHeaderStart) _insideConditional = true;
			else if (token.forEachHeaderStop) _insideConditional = false;

			if (token instanceof Array) {
				this.phase3Stack(token, stack, index, _labels, _insideConditional, _insideFunction, _insideIteration, _insideSwitch);
			} else {
				this.phase3Token(token, stack, index, _labels, _insideConditional, _insideFunction, _insideIteration, _insideSwitch);
			}
		},this);
	},
	phase3Stack: function(stack, parent, index, _labels, _insideConditional, _insideFunction, _insideIteration, _insideSwitch){
		this.phase3(stack, _labels, _insideConditional, _insideFunction, _insideIteration, _insideSwitch);
	},
	phase3Token: function(token, stack, index, _labels, _insideConditional, _insideFunction, _insideIteration, _insideSwitch){
		if (token.calledTargetToken && token.calledTargetToken.trackingObject && token.calledTargetToken.trackingObject.functionStack && token.calledTargetParen && token.calledTargetParen.expressionArg) {
			var fs = token.calledTargetToken.trackingObject.functionStack;
			var args = token.calledTargetParen.expressionArg.filter(function(s){ return s.desc == 'expression'; });
			var params = fs.paramNames;
			if (params) {
				args.some(function(arg, i){
					if (params[i]) this.setTypeToRef(params[i], this.getType(args[i]));
					else return true; // stops loop
				}, this);
			}
		}

		// process `this` after assignments (need chain). assignment is done on phase2stack down, so we need to put this here.
		if (token.value == 'this' && token.trackingObject && token.trackingObject.functionStack) this.trackThis(token, stack);

		// some param checking
		if (this.config.warnings && token.name == 2/*identifier*/) {
			this.confirmTimerNotEval(stack, token, index);
			this.confirmRadixForParseInt(stack, token, index);
		}

		if (token.value == 'prototype' && token.isPropertyOf && !token.isPropertyOf.isObjectLiteralStart && token.trackingObject.properties && !token.trackingObject.processedForConstructor) {
			this.processPrototypeForConstructor(token);
		}

		// since this is a property access, push object on the type stack for this var, unless this variable has been seen to be an Array or was checked elsewhere (do after assignments)
		if (token.dynamicPropertyAccess && !token.isObjectUnlessString && (!token.trackingObject || !token.trackingObject.varType || token.trackingObject.varType.indexOf('Array') < 0)) {
			this.setTypeToRef(token, 'Object');
		}

		if (token.isArrayUnlessString && (!token.trackingObject.varType || token.trackingObject.varType.indexOf('string') < 0)) {
			this.setTypeToRef(token, 'Array');
		}
		if (token.isObjectUnlessString && (!token.trackingObject.varType || token.trackingObject.varType.indexOf('string') < 0)) {
			this.setTypeToRef(token, 'Object');
		}
		if (token.isObjectUnlessPrimitive && token.trackingObject.varType && token.trackingObject.varType.indexOf('number') < 0 && token.trackingObject.varType.indexOf('string') < 0) {
			this.setTypeToRef(token, 'Object');
		}
		if (token.isObjectUnlessArray && (!token.trackingObject || !token.trackingObject.varType || token.trackingObject.varType.indexOf('Array') < 0)) {
			this.setTypeToRef(token, 'Object');
		}

		if (token.hadAssignment) {
			if (token.trackingObject) token.trackingObject.hadAssignment = true;
		}

		this.checkPragmasAfter(token);

		// were there any other types found than those declared by jsdoc?
		if (token.name == 2/*identifier*/ && token.meta == 'parameter' && token.trackingObject && token.trackingObject.jsdocType) {
			var one = token.trackingObject.jsdocType;
			var two = token.trackingObject.varType;
			// if length doesn't match, there's a problem.
			if (one.length != two.length) {
				token.jsdocIncomplete = true;
			// compare every item. shouldn't be many. like, one or two.
			} else if (!one.some(function(o){ return two.some(function(n){ return o == n; }); })) {
				token.jsdocIncomplete = true;
			}
		}

		this.findWarnings(token, _insideConditional, _labels, _insideIteration, _insideFunction, _insideSwitch);

/* it seems pointless to determine this at this point... :/
		// i assume that at this point functionStack is properly copied..?
		if (token.leadValue && token.tokenIsCalled) {
			var callTarget = token.callTarget
			var functionStack = callTarget && callTarget.trackingObject && callTarget.trackingObject.functionStack;
			var funcKeyword = functionStack && functionStack[0];
			
			console.log(["lead", callTarget, functionStack, funcKeyword])
		}
*/

		// gathering some "stats" for navigation
		// always last step of processing the token (move this if new stuff is added :)
		if (token.warning) this.collects.warnings.push(token);
	},

	trackThis: function(token, stack){
		// token has value this and has a tracking object which contains a functionStack (func scope for `this`).
		var to = token.trackingObject;
		if (to.processedThis) return; // only need to process each `this` once
		var fs = to.functionStack;
		var fspp = fs.prototypeProperties;
		if (!fspp) fs.prototypeProperties = fspp = {};
		// reference to the prototype tracking object
		token.protoObject = fspp;


		if (to.properties) {
			var objlitprop = fs.assignedToObjLitProperty;
//			console.log('func assigned to var?',[fs,fs.assignedToLead, objlit]);
			var copyAlsoToProto = null; // if there is such a ref, the prototype to which `this` belongs will be put in here.
			var isProtoTrackingObject = false;
			var copyAlsoToObjLit = null; // if no proto, at least try to find the object literal...
			// you can be assigned to a variable or a property. a property can be either that of a variable, other
			// property (/function call result) or object literal. we only distinct between last and the rest.
			if (objlitprop && objlitprop.assignedPropertyOf) {
//				console.log("prop obj assigned to", objlit.assignedPropertyOf);
				var propName = fs.assignedToObjLitProperty.assignedPropertyOf;
				if (propName.isPropertyOf && propName.value == 'prototype') {
//					console.log('prototype, copying properties...', to.properties);
					// ok! we have the ref of a prototype property token now. copy all properties of the `this` token to that ref
					copyAlsoToProto = propName; // processed below (for DRY).
				}
			} else if (fs.assignedToLead && fs.assignedToLead.isPropertyOf && fs.assignedToLead.isPropertyOf.value == 'prototype') {
				// ok! assigned to a property which is a direct property of prototype
				copyAlsoToProto = fs.assignedToLead.isPropertyOf; // processed below (for DRY).
			} else if (fs.assignedToLead && fs.assignedToLead.trackingObject && fs.assignedToLead.trackingObject.properties && fs.assignedToLead.trackingObject.properties.prototype) {
				// copy all properties to the prototype property tracking object
				// this is possibly a constructor.
				copyAlsoToProto = fs.assignedToLead.trackingObject.properties.prototype;
				isProtoTrackingObject = true;
			}

			if (!copyAlsoToProto && objlitprop && objlitprop.isPropertyOf) {
				// find ref of the object literal instead, if it exists
				// we do this here because the path (in the if above) of an objlit
				// that's assigned and that's not assigned (func call arg) is different
				copyAlsoToObjLit = objlitprop.isPropertyOf;
				if (!copyAlsoToObjLit.isObjectLiteralStart) {
					if (!this.hasError) console.warn('expected this to be the "tracking object" for this object literal... if there was no syntax error please check me.');
					return;
				}
			}


			// did we find a prototype target?
			if (isProtoTrackingObject) {
				if (!copyAlsoToProto.properties) copyAlsoToProto.properties = {};
				// copy properties to this object too
				copyAlsoToProto = copyAlsoToProto.properties;
			} else if (copyAlsoToProto) {
				if (!copyAlsoToProto.trackingObject) copyAlsoToProto.trackingObject = {};
				if (!copyAlsoToProto.trackingObject.properties) copyAlsoToProto.trackingObject.properties = {};
				// copy properties to this object too
				copyAlsoToProto = copyAlsoToProto.trackingObject.properties;
			}
			// copy found properties on `this` keyword to the prototype tracking object
			Object.keys(to.properties).forEach(function(name){
				if (name != '__proto__') {
					fspp[name] = to.properties[name];
					if (copyAlsoToProto) {
						if (copyAlsoToProto[name]) {
							// merge these two objects: copyAlsoToProto[name] and to.properties[name]
							// we keep copyAlsoToProto[name]
							var source = to.properties[name];
							var target = copyAlsoToProto[name];

							this.mergeProperties(source, target);

							fspp[name] = target;
							to.properties[name] = target;
						} else {
							copyAlsoToProto[name] = to.properties[name];
						}
					} else if (copyAlsoToObjLit) {
						// ok no prototype found. but if this `this` was found in a method of an object 
						// literal, we'll still assume that's a prototype object of some sort...

						if (copyAlsoToObjLit.definedProperties && copyAlsoToObjLit.definedProperties[name]) {
							// target is tracking object of the property of the objlit (prop has same name as the prop of a `this` we're checking)
							var source = to.properties[name];
							var target = copyAlsoToObjLit.definedProperties[name];
							if (target.trackingObject) {
								target = target.trackingObject;

								this.mergeProperties(source, target);

								to.properties[name] = target;
								// prevent warning...
								target.isDeclaredOnProto = true;
							} else {
								copyAlsoToObjLit.definedProperties[name] = source;
								source.isDeclaredOnProto = true;
							}
						}
					}
				}
			}, this);
		}

		to.processedThis = true;
	},
	mergeProperties: function(source, target){
		// copy refs and replace the tracking object in each source ref (TOFIX: this kinda screws up the saved refs, in properties array for some vars)
		if (!target.refs) target.refs = [];
		if (source.refs) source.refs.forEach(function(ref){
			ref.trackingObject = target;
			target.refs.push(ref);
		});
		// copy all the var types from source to target
		if (source.varType) {
			if (!target.varType) target.varType = source.varType;
			else source.varType.forEach(function(type){ if (target.varType.indexOf(type) < 0) target.varType.push(type); });
		}
		// likewise, copy all properties, if any
		if (source.properties) {
			if (!target.properties) target.properties = source.properties;
			else Object.keys(source.properties).forEach(function(prop){ if (!source.properties[prop]) target.properties[prop] = source.properties[prop]; });
		}
		// copy all remaining keys, if any. shouldn't be many.
		Object.keys(source).forEach(function(name){
			if (name != '__proto__' && name != 'refs' && name != 'varType' && name != 'properties') {
				// almost all tags might as well not exist if they are false.
				target[name] = target[name] || source[name];
			}
		});
	},
	confirmTimerNotEval: function(stack, token, i){
		// warn for timer string (literal...) argument
		if (token.leadValue && token.lastDotProperty == token && token.hasCallExpression && (token.value == 'setTimeout' || token.value == 'setInterval')) {
			var n = i;
			while (stack[++n] && stack[n].isWhite);
			// skip opening paren, but check to make sure that it is in fact that. makes sure we ignore weird stuff like parseInt.x()
			if (stack[n] && stack[n++].isCallExpressionStart && stack[n] && stack[n][0]) {
				// stack[n] should now be expressions
				// each expression is a parameter 
				// we want to inspect the first expression, to check if it's a number
				var type = this.getType(stack[n][0]);
				if (type == 'string') this.addWarning(token, 'timer eval');
			}
		}
	},
	confirmRadixForParseInt: function(stack, token, i){
		// check for radix parameter for parseInt
		if (token.leadValue && token.lastDotProperty == token && token.hasCallExpression && token.value == 'parseInt') {
			var n = i;
			while (stack[++n] && stack[n].isWhite);
			// skip opening paren, but check to make sure that it is in fact that. makes sure we ignore weird stuff like parseInt.x()
			if (stack[n] && stack[n++].isCallExpressionStart && stack[n]) {
				// stack[n] should now be expressions
				// each expression is a parameter 
				if (stack[n].expressionCount < 2) this.addWarning(token, 'missing radix');
			}
		}
	},
	processPrototypeForConstructor: function(token){
		var functionStack = token.isPropertyOf.functionStack || token.isPropertyOf.trackingObject && token.isPropertyOf.trackingObject.functionStack;
		if (functionStack) {
			var funcKeyword = functionStack[0];

			if (!funcKeyword.prototypeProperties) funcKeyword.prototypeProperties = {};

			var hop = Object.prototype.hasOwnProperty;
			for (var p in token.trackingObject.properties) {
				if (token.trackingObject.properties.hasOwnProperty(p) && token.trackingObject.properties[p].varType && p != '__proto__') {
					var objp = token.trackingObject.properties[p];
					if (hop.call(funcKeyword.prototypeProperties, p)) {
						funcKeyword.prototypeProperties[p] = funcKeyword.prototypeProperties[p].concat(objp.varType);
					} else {
						funcKeyword.prototypeProperties[p] = objp.varType.slice(0);
					}
				}
			}
		}

		// prevent double processing of this ref 
		token.trackingObject.processedForConstructor = true;
	},
	checkPragmasAfter: function(match){
		if (match.name == 2/*identifier*/) {
			// if this identifier was defined as a macro, flag it as such for later (and stop the some, because a matching macro has been found)
			this.pragmas.macros.some(function(pragma){
				if (pragma.pragmaNameLastPart == match.value) {
					// if the name of the macro is a property access, track back and verify that the entire name was used...
					var notFailed = true;
					if (pragma.pragmaNameParts.length > 1) {
						var part = pragma.pragmaNameParts.length-1; // skip the right-most version
						var treepos = match.tokposb;
						while (part-- && --treepos) {
							// now we must find a dot and then the next part. we're moving right-to-left in the token stream
							if (this.btree[treepos].value != '.' || this.btree[--treepos].value != pragma.pragmaNameParts[part]) {
								notFailed = false;
							}
						}
						if (!part) notFailed = false;

						//console.log(pragma.pragmaNameParts)
					}

					if (notFailed) match.isMacro = pragma;
				}
			}, this);
			this.pragmas.inlineNames.some(function(pragma){
				if (match.value == pragma.pragmaArg) {
					match.toBeInlined = pragma;
					return true;
				}
			});
		}

		// an ifdef can have a next/prev, otherwise the start/stop will determine the range
		// if it does have a next/prev, there is least one elseifdef or elsedef
		else if (match.isPragma && match.pragmaArg && match.pragmaStop && match.pragmaName == 'ifdef') {
			// process ifdef (and all attached elseifdefs and elsedefs)

			// check if the ifdef argument is defined
			match.isPragmaArgDefined = this.collects.defines.some(function(token){ return token.pragmaArg == match.pragmaArg; });

			var last = match; // link to "previous" in chain
			var found = match.isPragmaArgDefined; // determines whether else is used or removed, false if no (else)ifdefs are true
			if (match.pragmaElseIf) match.pragmaElseIf.forEach(function(token){
				// create doubly linked list
				token.pragmaPrev = last;
				last.pragmaNext = token;

				// check if this block is active or not
				token.isPragmaArgDefined = this.collects.defines.some(function(tmp){ return tmp.pragmaArg == token.pragmaArg; });
				// save result to determine elsedef
				found = found || token.isPragmaArgDefined;

				last = token;
			},this);
			// now check for the elsedef. does almost the same as elseifdef
			if (match.pragmaElse) {
				// linked list
				match.pragmaElse.pragmaPrev = last;
				last.pragmaNext = match.pragmaElse;

				// its only active when all previous elseifdefs and the ifdef were not active
				match.pragmaElse.isPragmaArgDefined = !found;

				last = match.pragmaElse;
			}
			// hookup the #endif to the previous pragma, if it wasnt an ifdef
			if (last.pragmaName != 'ifdef') {
				if (last.pragmaStart && last.pragmaStart.pragmaStop) {
					last.pragmaNext = last.pragmaStart.pragmaStop;
					last.pragmaStart.pragmaStop.pragmaPrev = last;
				}
			}
		} else if (match.isPragma && match.pragmaArg && match.pragmaStop && match.pragmaName == 'inline') {
			var tree = this.wtree;
			var startPos = match.tokposw +2;
			var lastPos = match.pragmaStop.tokposw;
			var lastLine = match.pragmaStop.startLineId;
			while (lastPos >= 0 && tree[lastPos].startLineId == lastLine) --lastPos;
			if (lastPos >= 0) ++lastPos; // newline, i think we should remove it anyways....

			// intermediate replacement. still needs indentation stripped
			var targets = tree.slice(startPos, lastPos);

			// figure out the indentation to remove it
			var wsCount = 0;
			var startLine = targets[wsCount].startLineId;
			var next = targets[wsCount];
			while (next && next.name == 9/*whitespace*/ && next.startLineId == startLine) next = targets[++wsCount];
			// now wsCount should be the number of tokens for indentation of the first line...

			// now remove that many tokens from the other lines, but stop at the first non-white token each line!
			var inlined = [];
			var lastLine = -1;
			var linePos = 0;
			var pos = 0;
			var next = targets[pos];
			while (pos < targets.length) {
				// if not white, add
				// if current line is equal and linepos is higher than whitespace count, add
				// if line terminator, add (regardless)
				if (next.name == 10/*lineterminator*/) {
					inlined.push(next);
					linePos = 0;
					lastLine = next.startLineId+1; // next line
				} else if (next.name != 9/*whitespace*/) {
					inlined.push(next);
					linePos = wsCount;
				} else if (linePos >= wsCount) {
					inlined.push(next);
				} else {
					++linePos;// not here! todisfix
				}
				next = targets[++pos];
			}

			match.pragmaValue = inlined;
		}

		if (match.isPragma && (match.pragmaName == 'ifdef' || match.pragmaName == 'inline') && !match.pragmaStop) {
			this.addWarning(match, 'pragma start missing end');
		}
	},
	findWarnings: function(token, _insideConditional, _labels, _insideIteration, _insideFunction, _insideSwitch){
		// ### warning stuff
		// http://www.jslint.com/msgs.html
		// http://www.jameswiseman.com/blog/2011/01/17/jslint-a-guide-to-jslint-messages/

		if (!this.config.warnings) return;

		var tree = this.btree;

		var next = tree[token.tokposb+1];
		var next2 = tree[token.tokposb+2];
		var prev = token.tokposb && tree[token.tokposb-1];

		// warn for assignment or weak comparison in statement headers
		if (_insideConditional) {
			// TOFIX: for iteration ;;
			if (token.value == '=') this.addWarning(token, 'assignment in header');
		}

		// make sure this only happens once
		if (token.isDevRelic) this.addWarning(token, 'is dev relic');

		// dead code is dead code
		if (token.deadCode) {
			this.addWarning(token, 'dead code');
		// catch all asi's
		}
		if (token.name == 13/*asi*/) {
			this.addWarning(token, 'ASI'); // this.collects.asis.push(this.btree[token.tokposb-1]); // collect prev match..
		}
		else if (token.name == 11/*PUNCTUATOR*/) {
			// check if header is static
			if (token.statementHeaderStart && !token.forHeaderStart && token.expressionArg.staticExpression) this.addWarning(token, 'static condition');
			// check if statement body is a block
			if (token.statementHeaderStop && !token.isForDoWhile) {
				// check if next token is '{', and if not if it has a newline preceeding.
				// that's all we need to know for now
				var stmtBodyStart = tree[token.tokposb +1];
				if (stmtBodyStart && stmtBodyStart.value != '{') {
					if (stmtBodyStart.newline && this.config['missing block bad']) this.addWarning(token, 'missing block bad');
					else if (!stmtBodyStart.newline && this.config['missing block good']) this.addWarning(token, 'missing block good');
				}
				// we take care of the special "else" case below
			}
			// if the property access is a string literal (and just that), warn.
			if (token.propertyAccessStart) {
				// dynamic property access: check if next token is string, followed by the ]
				if (next2 && next && next.isString && next2.propertyAccessStop) {
					// check if it could be an actually valid identifier (this will fail for any valid unicode stuff, but meh)
					if (/^[a-zA-Z\$_][a-zA-Z0-9\$_]*$/g.test(next.value.slice(1,-1))) this.addWarning(token, 'use dot access');
				}
			}
			// crock hates short
			if (token.value == '--' || token.value == '++') {
				if (prev.isCallExpressionStop) this.addWarning(token, 'cannot inc/dec on call expression');
				else if (next && next.valueType == 'call') this.addWarning(token, 'cannot inc/dec on call expression');
				// check if prev/next is primitive. dont worry about line terminators, semi's or ASI are part of the btree.
				else if ((next && (next.value == 'new' || next.isPrimitive)) || prev.isPrimitive) this.addWarning(token, 'inc/dec only valid on vars');
				else if (prev && prev.isGroupStop && prev.twin.numberOfExpressions > 1) {
					this.addWarning(token, 'comma in group makes inc/dec fail');
				}
				else if (next && next.isGroupStart && next.numberOfExpressions > 1) {
					this.addWarning(token, 'comma in group makes inc/dec fail');
				}
				
				this.addWarning(token, 'inc dec operator');
			}
			// crock doesnt like binary ops (probably "cause confusion in how they work")
			if (this.regexBinaryOps.test(token.value)) this.addWarning(token, 'binary operator');
			// use the force, always compare with === and !==
			if ((token.value == '==' || token.value == '!=') && !token.typeofOperator && this.config['weak comparison']) {
				this.addWarning(token, 'weak comparison');
			}
			// make sure op is not first token of tree
			if (token.tokposb) {
				// check for call lhs and anti-asi pattern '('
				if (token.isCallExpressionStart) {
					// if you're calling math, you're doing it wrong.
					if (prev.leadValue && prev.value == 'Math') this.addWarning(token, 'math call');
					// warn for possible asi problem pattern: a\n(b)
					if (token.newline) this.addWarning(token, 'bad asi pattern');

				}
				// dot property access
				if (token.value == '.') {
					if (prev.isNumber) {
						this.addWarning(token, 'number dot'); // (make sure script that starts with .3 doesnt error)
					} else if (prev.leadValue && next) {
						// check for property access on objects or whatever
						if (prev.value == 'document' && (next.value == 'write' || next.value == 'writeln')) {
							this.addWarning(next, 'document.write');
						}
					}
				}
				// check for /= and various assignment gimmicks
				if (token.isAssignment) {
					if (token.value == '/=') this.addWarning(token, 'regex confusion');
					if (prev.value == 'this') this.addWarning(token, 'assignment this');
					if (prev.trackingObject && prev.trackingObject.isCatchVar) this.addWarning(token, 'catch var assignment');
				}
				// warn for confusing plusses or minusses
				if (token.value == '+' || token.value == '++') {
					if (prev.value == '+' || prev.value == '++') this.addWarning(token, 'confusing plusses');
				}
				else if (token.value == '-' || token.value == '--') {
					if (prev.value == '-' || prev.value == '--') this.addWarning(token, 'confusing minusses');
				}
				// jslint doesn't like double bang in comparisons. but that's too complex for now. TOFIX
				//else if (token.value == '!' && token.tokposb && prev.value == '!') token.warning = 'double bang';
				// double comma
				if (token.value == ',' && next && next.value == ',') this.addWarning(token, 'extra comma');
				// detect trailing comma's, which have very bad support.
				if (token.value == ',' && next && (next.value == ']' || next.value == '}')) this.addWarning(token, 'trailing comma');
			}
			// there's a stronger operator on the same level as this one...
			if (token.isAmbiguous) this.addWarning(token, 'multiple operators on same level');
			// we want to warn for empty statements
			if (token.emptyStatement) this.addWarning(token, 'empty statement');
		}
		// label checker
		else if (token.isLabel) {
			// if declared, may not be declared twice
			// if used, must be declared
			// label targets must be in same scope
			if (token.isLabelDeclaration) {
				// scope labels must exist (for this is a declaration) and the label must be found at least once...
				if (this.findLabel(token.value, _labels, true) == 2) this.addWarning(token, 'duplicate label');
			} else if (!this.findLabel(token.value, _labels)) {
				this.addWarning(token, 'label not found');
			}
			this.addWarning(token, 'is label');
		}
		else if (token.name == 2/*identifier*/) {
			// used a variable before declaring it (except for function declarations...)
			if (token.prematureUsage && !token.trackingObject.isFuncDecl) this.addWarning(token, 'premature usage');
			// declared variables that have only been declared, never actually used
			if (token.unused) this.addWarning(token, 'unused');
			// dangling underscore? (i actually think this was a warning for html crap :)
			if (token.value[token.value.length-1] == '_' && !this.hasWarning(token,'dont use __proto__') && token.meta != 'parameter' && !token.isCatchVar) this.addWarning(token, 'dangling underscore');
			// continue (regardless of label) may only occur inside an iteration
			if (!_insideIteration && token.value == 'continue') this.addWarning(token, 'continue only in loops');
			// break without label is only valid inside switch/iteration. with label is always fine (label error is shown elsewhere)
			if (token.value == 'break' && !_insideIteration && !_insideSwitch && !(next && next.isLabel)) this.addWarning(token, 'break needs loop or label');
			// return statement is ever only allowed within a function
			if (!_insideFunction && token.value == 'return') this.addWarning(token, 'return only in function');
			// caller and callee are so 1995
			if (token.isPropertyName && (token.value == 'caller' || token.value == 'callee')) this.addWarning(token, 'caller callee');
			// usage of undefined
			if (token.value == 'undefined') this.addWarning(token, 'undefined');
			// trying to use built in objects as constructor
			if (prev && this.regexBuiltinBadConstructors.test(token.value) && prev.value == 'new') {
				if (token.value == 'Math' || token.value == 'JSON') this.addWarning(token, 'very bad constructor');
				else if (token.value == 'Error') this.addWarning(token, 'error constructor');
				else if (token.value == 'Array') this.addWarning(token, 'array constructor');
				else this.addWarning(token, 'bad constructor');
			}
			// constructors should not be directly called
			if (token.isConstructor && token.value != 'function' && next && next.value == '(' && !next.parensBelongToNew) this.addWarning(token, 'constructor called as function');
			// (same, for tracking object)
			if (token.trackingObject && token.trackingObject.isConstructor && !token.functionStack && next && next.value == '(' && !next.parensBelongToNew) {
				this.addWarning(token, 'constructor called as function');
			}
			// Function results in eval of body
			if (token.value == 'Function' && token.leadValue && next && next.value == '(') this.addWarning(token, 'Function is eval');
			// new Object
			if (prev && token.value == 'Object' && token.leadValue && prev.value == 'new') this.addWarning(token, 'use {}');
			// new Array (TOFIX: ignore if a single number arg was given...)
			if (prev && token.value == 'Array' && token.leadValue && prev.value == 'new') this.addWarning(token, 'use []');
			// new new
			if (prev && token.value == 'new' && token.tokposb && prev.value == 'new') this.addWarning(token, 'double new');
			// delete delete
			if (token.value == 'delete' && prev && prev.value == 'delete') this.addWarning(token, 'double delete');
			// "eval is evil"
			if (token.value == 'eval') this.addWarning(token, 'eval');
			// only use in inside the for-header
			if (token.value == 'in' && !token.forFor) this.addWarning(token, 'in out of for');
			// else block check
			if (token.value == 'else') {
				// check if prev token is not a dot, in that case it's just (a very stupid..) property access and we do nothing here (except warn perhaps, but elsewhere)
				var before = tree[token.tokposb - 1];
				if (before && before.value != '.') {
					// check if next token is neither '{' nor 'if', and if not whether it has a newline preceeding.
					var stmtBodyStart = tree[token.tokposb + 1];
					if (stmtBodyStart && stmtBodyStart.value != '{' && stmtBodyStart.value != 'if') {
						if (stmtBodyStart.newline) {
							if (this.config['missing block bad']) this.addWarning(token, 'missing block bad');
						} else {
							if (this.config['missing block good']) this.addWarning(token, 'missing block good');
						}
					}
				}
			}
			// were there any other types found than those declared by jsdoc?
			if (token.jsdocIncomplete) this.addWarning(token, 'jsdoc type mismatch');
			// using parentheses in silly places
			if ((token.value == 'typeof' || token.value == 'throw' || token.value == 'return' || token.value == 'delete' || token.value == 'new' || token.value == 'void') && next && next.value == '(') {
				this.addWarning(next, 'unnecessary parentheses');
			}

			// using uninitialized variable in loop
			if (
				_insideIteration &&
				!token.isPropertyOf &&
				!token.isPropertyName &&
				!token.hadAssignment &&
				token.trackingObject &&
				!token.trackingObject.hadAssignment &&
				!token.trackingObject.isFuncParameter &&
				!token.trackingObject.isEcma &&
				!token.trackingObject.isBrowser &&
				!token.trackingObject.implicit
			) {
					this.addWarning(token, 'uninitialized value in loop');
			}
			// check if instance properties were explicitly declared on the prototype
			// we'll also accept it if the property was declared on an object literal, a common pattern which might not involve an explicit prototype
			else if (token.value == 'this' && next && next.value == '.' && next2 && next2.trackingObject && !next2.trackingObject.isDeclaredOnProto) {
				// only report if it's very likely that it makes sense. for some this instances we cant really track the prototype object
				// for instance those functions passed on to lambda methods (forEach, map, etc). it's going to be difficult to determine
				// the proper prototype context in certain (common) cases. so we will not even bother.
				if (token.trackingObject.functionStack && (
					token.trackingObject.functionStack.assignedToObjLitProperty ||
					token.trackingObject.functionStack.assignedToLead ||
					token.trackingObject.functionStack.desc == 'func decl'
				)) {
					this.addWarning(token, 'prop not declared on proto');
				} else {
					token.trackingObject.cannotDeterminePrototype = true;
				}
			}
		}
		else if (token.isNumber) {
			// leading or trailing dot in numbers
			if (token.value[0] == '.')  this.addWarning(token, 'leading decimal');
			else if (token.value[token.value.length-1] == '.')  this.addWarning(token, 'trailing decimal');
			// this is actually not possible as long as the parser blocks octals entirely (leading zero padding)
			if (token[0] == '0' && token.length > 1 && token[1] != '.' && token[1] != 'e') this.addWarning(token, 'octal escape');
			if (token[0] == '0' && token[1] == '0') this.addWarning(token, '00');
			// hex are bad, mkay
			if (token.name == 3/*NUMERIC_HEX*/) this.addWarning(token, 'avoid hex');
		}
		else if (token.name == 1/*REG_EX*/) {
			// crock doesnt like dots and exceptions. "they dont explicitly say what you want to match and are therefor considered insecure"
			if (token.value.indexOf('.') >= 0 || token.value.indexOf('[^') >= 0) this.addWarning(token, 'dot and not can be confusing');
			// regex having empty char set
			if (token.value.indexOf('[]') >= 0) this.addWarning(token, 'empty regex char class');
			// warn for regular expression calls
			if (next && next.value == '(') this.addWarning(next, 'regexp call');
			// check for bad escapes
			if (this.regexRegexEscapement.test(token.value)) this.addWarning(token, 'unlikely regex escapement');
			// invalid unicode escapes
			if (this.regexInvalidUnicodeEscape.test(token.value)) this.addWarning(token, 'invalid unicode escape in regex');
			// invalid hex escapes
			if (this.regexInvalidHexEscape.test(token.value)) this.addWarning(token, 'invalid hex escape in regex');
			// control chars in regex
			if (this.regexControlChars.test(token.value)) this.addWarning(token, 'control char');
			// "unsafe character"s
			if (this.regexUnsafeCharacters.test(token.value)) this.addWarning(token, 'unsafe char');
		}
		// look for escape sequences that are not explicitly defined in the spec. they're not disallowed, but still (only '"\bfnrtv are explicitly defined in SingleEscapeCharacter, and \u for unicode)
		else if (token.isString) {
			if (this.regexStringEscapement.test(token.value)) this.addWarning(token, 'bad string escapement');
			// control chars in string
			if (this.regexControlChars.test(token.value)) this.addWarning(token, 'control char');
			// "unsafe character"s
			if (this.regexUnsafeCharacters.test(token.value)) this.addWarning(token, 'unsafe char');
			// invalid unicode escapes
			if (this.regexInvalidUnicodeEscape.test(token.value)) this.addWarning(token, 'invalid unicode escape in string');
			// invalid hex escapes
			if (this.regexInvalidHexEscape.test(token.value)) this.addWarning(token, 'invalid hex escape in string');
		} else if (token.isComment) {
			// probably only bad for multi-line comment. but warn regardless.
			if (token.value.indexOf('/*',1) > 0 || (token.name == 7/*COMMENT_SINGLE*/ && token.value.indexOf('*/',1) > 0)) this.addWarning(token, 'nested comment');
		}

		// ### end warning stuff
	},
	findLabel: function(label, labels, twice){
		var n = 0;
		for (var i=0; i<labels.length; ++i) {
			if (labels[i] instanceof Array) n += this.findLabel(label, labels[i]);
			else if (labels[i] == label) ++n;

			if (!twice && n) break;
			if (twice && n == 2) break;
		}
		return n;
	},

	extraTyping: function(stack){
		this.phase4(stack);

		// typerefs are only defined on function keyword tokens or tracking objects.
		var rest = this.btree.filter(function(token){ return token.typeRefs || (token.trackingObject && token.trackingObject.typeRefs); });
		rest = rest.filter(function(token,index){ return token.isFuncExprKeyword || token.isFuncDeclKeyword || !rest.some(function(tok, i){ return i>index && tok.trackingObject == token.trackingObject; }); }); // unique

		// rest now only contains vars or props
		// typeRefs may contain (optionally invoked) vars or props or simply strings signifying the type

		while (rest && rest.length) {
			var change = true;
			while (change) {
				change = false;
				rest = rest.filter(function(token){
					var isFunction = token.isFuncExprKeyword || token.isFuncDeclKeyword;
					var to = token.trackingObject;
					var typerefs = (isFunction?token.typeRefs:to&&to.typeRefs);
	
					if (typerefs) {
						typerefs = typerefs.filter(function(ref){
							if (!ref) {
								change = true;
								return false;
							}
							if (typeof ref == 'string') {
								if (isFunction) {
									if (!token.varType) token.varType = [];
									if (token.varType.indexOf(ref) < 0) token.varType.push(ref);
								} else {
									if (!to.varType) to.varType = [];
									if (to.varType.indexOf(ref) < 0) to.varType.push(ref);
								}
								change = true;
								return false;
							}
							if (!ref.trackingObject) { // happens on property names right after dynamic access or calls or obj/arr literals
								if (!ref.isPropertyName) console.log("happens (i want to investigate this...)", ref)
								if (!ref.tokenIsCalled) { // for calls we need the functionStack which is should be stored in the trackingObject which doesnt exist (so, meh?)
									if (ref.varType && ref.varType.length) {
										// merge ref var types into to
										ref.varType.forEach(function(o){
											if (to.varType.indexOf(o) < 0) {
												to.varType.push(o);
											}
										},this);
									}
								}
								change = true;
								return false; // remove this token from the typeRef, you're done with it
							}
	
							// pretty much the same, except use varType on the trackingObject instead
							if (!ref.trackingObject.typeRefs && !ref.typeRefs) {
								// we are going to copy all var types from ref to token.
								// if it was a call, go to the return types of that function instead...
								if (ref.tokenIsCalled) {
									if (ref.trackingObject && ref.trackingObject.functionStack) {
										var func = ref.trackingObject.functionStack[0];
										if (!func) return true; // meh, error?
										if (func.typeRefs) return true; // dont remove this yet... we're not done with it yet!
										if (func.varType) {
											// this is the return type for the function. add those to the types of token and its tracking object...
											var vars = func.varType;
											if (!vars || !vars.length) vars = ['\u00BF'];
											vars.forEach(function(o){
												if (!token.varType) token.varType = [];
												if (token.varType.indexOf(o) <0) token.varType.push(o);
												if (!isFunction) {
													if (!to.varType) to.varType = [];
													if (to.varType.indexOf(o) < 0) to.varType.push(o);
												}
											},this);
										}
									} else {
										if (!token.varType) token.varType = [];
										if (token.varType.indexOf('\u00BF') <0) token.varType.push('\u00BF');
										if (!isFunction) {
											if (!to.varType) to.varType = [];
											if (to.varType.indexOf('\u00BF') < 0) to.varType.push('\u00BF');
										}
									}
	//								console.log("fetching", ref)
								} else {
									var vars = ref.trackingObject.varType;
									if (!vars || !vars.length) vars = ['\u00BF'];
									// merge ref var types into to
									vars.forEach(function(o){
										if (!token.varType) token.varType = [];
										if (token.varType.indexOf(o) < 0) token.varType.push(o);
										if (!isFunction) {
											if (!to.varType) to.varType = [];
											if (to.varType.indexOf(o) < 0) to.varType.push(o);
										}
									},this);
								}
								change = true;
								return false; // remove this token from the typeRef, you're done with it
							}
							// otherwise hope the types of this object are fixed in the next round, if any
							return true;
						},this);
						
						if (!typerefs.length) {
							if (isFunction) delete token.typeRefs;
							else delete to.typeRefs;
							return false;
						}
	
						if (isFunction) token.typeRefs = typerefs;
						else to.typeRefs = typerefs;
					}
					return false;
				},this);
			}
			
			if (rest.length) {
				// ok... just pick the first variable we couldnt determine and retry the others, see if that works now
				var failed = rest[rest.length-1];
				// setting one of the refs to a string will add it as a type and propagate accordingly.
				if (failed.typeRefs) failed.typeRefs[0] = '\u00BF';
				else failed.trackingObject.typeRefs[0] = '\u00BF';
			}
		}
		
	},
	phase4: function(stack){
		// ok so we're going to look for types of variables and properties...
		stack.forEach(function(token){
			if (token instanceof Array) {
				this.phase4Stack(token, stack);
			}
		},this);
	},
	phase4Stack: function(stack, parent){
		// tofix: also check for array types and object literals (also assignment)
		if (stack.desc == 'expression' && stack.sub == '=') {
//			console.log("Expr")
			var assignee = this.getAssigneeForTyping(stack[0]);
			var refs = stack[2];
		} else if (stack.desc == 'single var decl') {
//			console.log(["decl", parent, stack])
			var assignee = this.getAssigneeForTyping(stack[0]);
			var refs = stack[2];
		}

		if (assignee && refs) {
			if (!refs) console.log('no refs', [stack]);
			if (assignee && assignee.trackingObject) {
				var typeRefs = this.getTypeRefs(refs);
				if (typeRefs && typeRefs.length) {
					if (!assignee.trackingObject.typeRefs) assignee.trackingObject.typeRefs = [];
					var tr = assignee.trackingObject.typeRefs;
					typeRefs.forEach(function(r){
						if (tr.indexOf(r) < 0) tr.push(r);
					});
				}
			}
		}

		if (stack.sub == 'return' && stack.returnFor) {
			// re-evaluate all return types
			if (!stack.returnFor.typeRefs) stack.returnFor.typeRefs = [];
			var expr = stack.filter(function(s){ return s.desc == 'expressions'; });
			if (expr.length) {
				expr = this.getTypeRefs(expr[0]);
				if (expr) expr.forEach(function(o){
					stack.returnFor.typeRefs.push(o); 
				});
			} else { 
				stack.returnFor.typeRefs.push('undefined');
			}
		}

		this.phase4(stack);
	},

	getAssigneeForTyping: function(stack){
		if (stack.isVarKeyword) return stack;
		if (stack.desc == 'grouped') {
//			console.log("group")
			var exprs = null;
			stack.some(function(o){
				if (o.desc == 'expressions') {
					exprs = o;
					return true;
				}
			});
			if (!exprs) return false; // eh, empty group? bad assignment
			if (exprs.expressionCount != 1) return false; // bad assignment
//			console.log("testing on", exprs[0])
			return this.getAssigneeForTyping(exprs[0]);
		}
		if (stack.desc == 'sub-expression') {
//			console.log("subexpr")
			var next = this.btree[stack.nextBlack];
			if (next) {
				if (next.varNameDecl) return next;
				if (next.valueType != 'lead' && next.valueType != 'dot') {
					return false; // only lead and dot value types have a tracking object, which we need
				}
				if (next.isUnaryOp) { // something like delete, new, ++, --, +, -, ~, !, etc. you cant assign to that.
//					console.log("invalid assignment")
					return false; // invalid assignment
				}
				if (stack[0].desc == 'grouped') {
					return this.getAssigneeForTyping(stack[0]);
				}
				if (next.name != 2/*identifier*/) {
//					console.log("expecting identifier (might be array/object literal)");
					return false;
				}
//				console.log("lead is ", next.lastPropertyNameWithoutSolidChainToLead || next);
				return next.lastPropertyNameWithoutSolidChainToLead || next;
			} else {
				return false;
			}
		}
		if (stack.desc == 'expression') {
//			console.log(["exprrrr", stack])
			if (stack.length == 1 && stack[0].desc == 'sub-expression') return this.getAssigneeForTyping(stack[0]);
			return false;
		}
		console.log(["dunno what to do now", stack, parent]);
	},
	getTypeRefs: function(stack){
		//console.log(["todo2", stack.desc, stack]);

		// || && ?: ()
		// anything else returns a determinable type of some sort. of course, += and + might
		// return two types, but we wont care about that for now. just add both.

		// the return type might be a call or a non-call value. return types are obviously more difficult to compute.

//		console.log(['get ref',stack.desc]);
		if (stack.desc == 'expressions') {
			// only the last value of any expressions is ever actually returned.
			var exprs = stack.filter(function(s){ return s.desc == 'expression'; });
			return this.getTypeRefs(exprs.pop());
		}
		if (stack.desc == 'expression') {
			if (stack.length == 1) {
				return this.getTypeRefs(stack[0]);
			}
			if (stack.length == 3) {
				switch (stack.sub) {
					case '?':
						var a = this.getTypeRefs(stack[2][0]);
						var b = this.getTypeRefs(stack[2][2]);
						if (!a) return b;
						if (!b) return a;
						return a.concat(b);
					case '||': // overflows
					case '&&':
						var a = this.getTypeRefs(stack[0]);
						var b = this.getTypeRefs(stack[2]);
						if (!a) return b;
						if (!b) return a;
						return a.concat(b);
					case 'in':
					case 'instanceof':
					case '!=':
					case '!==':
					case '==':
					case '===':
					case '>':
					case '<':
					case '<=':
					case '>=':
						var r = ['boolean'];
						return r;
					case '+':
					case '+=':
						var r = ['string','number'];
						return r;
					default:
						var r = ['number'];
						return r;
				}
			}
		}
		if (stack.desc == 'sub-expression') {
//			console.log("subexpr")
			var next = this.btree[stack.nextBlack];
			if (next) {
				if (next.isFuncExprKeyword) return ['Function']; // tofix: immediately invoked func expr, or some weird ++ bs...
				if (next.valueType == '+-') return ['number'];
				if (next.valueType == '?' || next.valueType == 'dynamic') return false; // cant determine shit here. except maybe arrays... but that's a tofix for sure :)
				// searching for lead, dot or call 
				if (next.isUnaryOp) { // something like delete, new, ++, --, +, -, ~, !, etc. you cant assign to that.
					if (next.value == 'new') return ['Object']; // TOFIX: should be the actual type you're new-ing
					if (next.value == 'delete' || next.value == '!') return ['boolean'];
					if (next.value == 'typeof') return ['string'];
					if (next.value == '++' || next.value == '--' || next.value == '~' || next.value == '+' || next.value == '-') return ['number'];
					if (next.value == 'void') return ['undefined'];
					return false; // invalid assignment
				}
				if (stack[0].desc == 'grouped') {
					return this.getTypeRefs(stack[0]);
				}

				if (next.isNumber) return ['number'];
				if (next.isString) return ['string'];
				if (next.value == 'true' || next.value == 'false') return ['boolean'];
				if (next.value == 'null') return ['null'];
				if (next.value == 'undefined') return ['undefined'];
				if (next.name == 1/*regexp*/) return ['RegExp'];
				if (next.isArrayLiteralStart) return ['Array'];
				if (next.isObjectLiteralStart) return ['Object'];

				if (next.name != 2/*identifier*/) {
//					console.log("expecting identifier (might be array/object literal)");
					return false;
				}
				if (next.valueType == 'lead') {
					return [next];
				}
				// ok. either it's a call:
				if (next.calledTargetToken) return [next.calledTargetToken]; // returns last property that preceeded the parenthesis at the end.
				// or a dot property at the end or simply the lead value itsel... we knifed all other cases
				return [next.lastPropertyNameWithoutSolidChainToLead || next];
			} else {
				return false;
			}
		}
		if (stack.desc == 'grouped') {
			var exprs = stack.filter(function(o){ return o.desc == 'expressions'; })[0];
			if (exprs) {
				exprs = exprs.filter(function(o){ return o.desc == 'expression'; });
				if (exprs) {
					return this.getTypeRefs(exprs.pop());
				}
			}
		}
		if (!this.hasError) console.log(["unhandled case", stack.desc, stack]);
	},

	getType: function(stack){
		if (this.hasError && !stack) return false; // can sometimes happen, ex: `[a.[`
		if (stack.desc == 'expressions') return this.getTypeExpressions(stack);
		else if (stack.desc == 'expression') return this.getTypeExpression(stack);
		else if (stack.desc == 'sub-expression') return this.getTypeSubExpression(stack);
		else if (stack.name == 14/*error*/) return false;
		if (!this.hasError) {
			throw console.log("error:",[stack, this.lastInput]),'what kind of stack is this';
		}
		return false;
	},
	getTypeExpressions: function(stack){
		if (stack.desc != 'expressions') {
			if (!this.hasError) console.log([stack, "bad stack", this.lastInput]);
		}
		// the type of an expressions tree is the type of the last expression
		var n = stack.length;
		while (n--) if (stack[n].desc == 'expression') return this.getTypeExpression(stack[n]);
		throw "empty expressions?";
	},
	getTypeExpression: function(stack){
		if (stack.desc != 'expression') throw 'should be expr';

		// can be several things...
		if (stack.length == 1) return this.getType(stack[0]);

		if (stack.sub == '?') {
			// special case. the return type is actually only the result of the right expression
			return this.getType(stack[2]);
		}

		if (stack.length == 3) {
			if (stack.desc != 'expression') {
				if (!this.hasError) throw console.log(stack,[]),"expr struct check";
				return false;
			}
			var op = stack[1][0].value;
			var type = this.getBinaryOperatorType(op); // get operator token
			if (typeof type == 'string') return type;

			var lhs = stack[0];
			var rhs = stack[2];

			// determine type of lhs and rhs
			var right = this.getType(rhs);

			// for simple assignment, it's always the rhs type
			if (op == '=') return right;

			var left = this.getType(lhs);

			// there are two possibilities:
			// either the op is + or += and the result is string or number, depending on BOTH operands
			// or the op is an op that can return either of the types of the operand (&&, etc)
			// for +, its string if left or right is a string. but if it's an object, it will first
			// check for valueOf, then toString. The first result type determines total result (note
			// that toString may still return a number and valueOf may still return a string)
			// (ps. the rule is simpler: if left and right are number, return number. otherwise string)
			// ex: ({toString:function(){return "a"},valueOf:function(){return 5}})+5

			// HOWEVER... we are going to assume objects get converted to strings first. we'll add an
			// option to override this behavior to either way. TOFIX

			//console.log("left", left, "right", right, "op", op, type);
			if (op == '+' || op == '+=') {
				if (left == 'number' && right == 'number') return 'number';
				if (left == 'string' || right == 'string') return 'string';

				// now check for arrays. filter out all types except for string or number.
				if (left instanceof Array) {

				}
				if (right instanceof Array) {

				}

				return ['string','number'];
			}

			if (!left) return right;
			if (!right) return left;

			return [].concat(left, right);
		}

//		if (!this.hasError) throw console.log("expr len check fail ("+stack.length+")", [stack.slice(0), stack]);
		return false;
	},
	/**
	 * Get return type when applying this operator to any two values. Returns false when the op returns the left or right type.
	 * 
	 * @param {string} op
	 * @return {Array|string|boolean}
	 */
	getBinaryOperatorType: function(op){
		if (op == '+'|| op == '+=') return ['number','string']; // depends on left and right side. if either is a string, so is the result.
		else if (this.regexBoolOps.test(op)) return 'boolean';
		else if (this.regexNumberOps.test(op)) return 'number';
		return false;
		// && || and ?: do not appear in this list, their result is unpredictable. 
		// the , is weaker than any assignment
	},
	getTypeSubExpression: function(stack){
		//console.log("expression",[stack], stack[0].isString)
		// sub-expressions consist of unary operators, a lead value, property access and call suffix
		// try unary operators first. most of them determine the type before hand (l-t-r)

		var lastType = false;
		var unaries = true;
		for (var i=0; i<stack.length; ++i) {
			if (unaries) unaries = true;

			var token = stack[i];
			if (token.leadValue || token.isArrayLiteralStart || token.isObjectLiteralStart) unaries = false;

			if (token.desc == 'grouped') {
				lastType = this.getTypeGroup(stack[i]);
				unaries = false;
			} else if (token.desc == 'sub-expression') {
				lastType = this.getTypeSubExpression(token);
				unaries = false;
			} else if (token.desc == 'expression') {
				lastType = this.getTypeExpression(token);
				unaries = false;
			} else if (token.isFunction) {
				lastType = token;
				unaries = false;
			} else if (token.value == '.') {
				lastType = false;
				unaries = false;
			} else if (token.value == '[' && !token.isArrayLiteralStart) {
				return false; // anything can happen now... (best case it has a postfix operator..)
			} else if (token.value == '(') {
				if (!token.parensBelongToNew) return false; // as long as we dont know function return types ahead of time, we cant determine anything after a function call
			} else if (token.value == ')') {
				// ignore
			} else if (token.value == '++' || token.value == '--') {
				lastType = 'number';
				unaries = false;
			} else if (unaries) {
				if (token.name == 14/*error*/) return false;
				else if (token.isUnaryOp || token.value == '--' || token.value == '++') {
					// unary operators can determine the type for the whole expression right here :)
					if (token.value == '!' || token.value == 'delete') lastType = 'boolean';
					else if (token.value == '~' || token.value == '+' || token.value == '-' || token.value == '++' || token.value == '--') lastType = 'number';
					else if (token.value == 'void') lastType = 'undefined';
					// TOFIX this is a problem. we'll need to determine the rest of the expression and stuff later
					else if (token.value == 'new') {
						// new operators should always have their bound expression set by processNew
						if (token.targetExpression) lastType = token;
						// if you're typing new, there wont be an expression
						//throw console.log(['error',token]),'missing target expression new';
					}
					else if (token.value == 'typeof') lastType = 'string';
				}
				else if (!token.isWhite) {
					if (!this.hasError) throw console.log(["error",stack, token, this.lastInput]), "wtf?";
					return false;
				}
			} else { // lead value, array- or object literal now
				// the postfix overrules any lead value (but not unaries)
				if (token.hasPostfix) lastType = 'number';

				else if (token.isNumber) lastType = 'number';
				else if (token.isString) lastType = 'string';
				else if (token.name == 1/*reg_exp*/) lastType = 'regexp';
				else if (token.value == 'null') lastType = 'null';
				else if (token.value == 'true' || token.value == 'false') lastType = 'boolean';

				// did we find a property during trackProperties?
				if (token.leadValueTarget) {
					// get the actual property that's the target
					if (token.leadValueTarget !== true) token = token.leadValueTarget;
					// if no property (highly likely), return the array- or object stack
					else if (token.isArrayLiteralStart) lastType = token;
					// return objlit stack
					else if (token.isObjectLiteralStart) {
						if (!token.definedProperties) throw 'no props?'; // even on an empty objlit this array should exist (albeit empty)
						// return the token, that way we can bind a ref later (if we want to). just make sure this is not an array. 
						// see addTypeToVarStack()
						lastType = token;
					}
					else if (token.trackingObject && token.trackingObject.varType) {
						// TODO: make sure that this cant introduce duplicate types in a varType array...
						if (token.trackingObject.properties) lastType = token.trackingObject.varType.concat(token);
						else lastType = token.trackingObject.varType;
					}
					else if (token.trackingObject && token.trackingObject.properties) lastType = token;
				}
				// if no property (highly likely), return the array- or object stack
				else if (token.isArrayLiteralStart) lastType = token;
				// return objlit stack
				else if (token.isObjectLiteralStart) {
					if (!token.definedProperties) {
						if (this.hasError) return lastType; // happens for instance with: `d[{`
						else throw 'no props?'; // even on an empty objlit this array should exist (albeit empty)
					}
					// return the token, that way we can bind a ref later (if we want to). just make sure this is not an array. 
					// see addTypeToVarStack()
					lastType = token;
				}
			}
		}

		return lastType;
	},
	getTypeGroup: function(stack){
		var i = 0; // skips the first element, which should be the opening paren (
		// first non-white token should be expressions. else prolly eof
		while (stack[++i]) if (!stack[i].isWhite) return this.getTypeExpressions(stack[i]);
		if (!this.hasError) console.log(["not found?",this.lastInput]);
		return false;
	},

	/**
	 * Add types to the possible types of ref. If types is or contains a (plain)
	 * object, all its (own) properties are added to ref.properties .
	 * 
	 * @param {Object} ref
	 * @param {string|Object|Array} types Either a string (with the type), an object (a token, Function, Array or Object literal) or an array with this mix
	 * @param {boolean} [noTracking] Do not add type to trackingObject of ref
	 */
	setTypeToRef: function(ref, types, noTracking){
		if (!ref.varType) ref.varType = [];
		this.addTypeToVarStack(ref, types);

		if (!noTracking) {
			if (!ref.trackingObject) {
				if (!ref.isPrimitive && !this.hasError) {
					console.error(["no tracking obejct?", types]);
					console.trace();
				}
				return; // this will hurt...
			}
			if (!ref.trackingObject.varType) ref.trackingObject.varType = [];
			this.addTypeToVarStack(ref.trackingObject, types, true);
		}
	},
	/**
	 * Actually hook the types to either the token or a its tracking object (we wont know)
	 * 
	 * @param {Object} ref token or tracking object
	 * @param {string|Object|Array} types Either a string (with the type), an object (a token, Function, Array or Object literal) or an array with this mix
	 * @param {boolean} [copyProps] if true, you're working with a trackingObject and obj lit properties should also be copied
	 */
	addTypeToVarStack: function(ref, types, copyProps){
		var vartypes = ref.varType;
		if (typeof types == 'string') {
			if (vartypes.indexOf(types) < 0) {
				// check if Object, dont assign if tracking has function/array
				if (types == 'Function' || types == 'Array') {
					do {
						var pos = vartypes.indexOf('Object');
						if (pos >= 0) vartypes.splice(pos, 1);
					} while (pos >= 0);
				}

				if (types != 'Object' || (vartypes.indexOf('Array') < 0 && vartypes.indexOf('Function') < 0)) {
					vartypes.push(types);
				}
			}
		} else if (types.value == 'new') {
			var te = types.targetExpression;
			// expr is false if it was something weird like a function call
			if (te === false) {
				this.addTypeToVarStack(ref, 'unknown-new-object');
			} else {
				if (!te) throw 'why this new have no target expression?';
				if (!te.constructorName) throw 'why is htere no constructor name?';
				this.addTypeToVarStack(ref, te.constructorName);
				ref.targetPrototype = te;
			}
		} else if (types.isFunction) {
			this.addTypeToVarStack(ref, 'Function');
		} else if (types.isArrayLiteralStart) {
			this.addTypeToVarStack(ref, 'Array');
		} else if (types instanceof Array) {
			types.forEach(function(type){
				this.addTypeToVarStack(ref, type, copyProps);
			},this);
		} else if (types) {
			// object (token), copy properties and reference to token
			this.addTypeToVarStack(ref, 'Object', false, true);

			if (copyProps) { // (ref = trackingObject)
				// types is actually the object literal opening match ({)
				// get the defined properties on that object literal
				var defprops = types.definedProperties;
				for (var key in defprops) {
					if (defprops.hasOwnProperty(key)) {
						// generate property array to track references
						if (!ref.properties) ref.properties = {};
						var props = ref.properties;

						// add to properties of ref
						if (this.hasOwn(props, key)) props[key].refs.push(defprops[key]);
						else props[key] = {value:key, refs:[defprops[key]], varType:defprops[key].varType};

						// attach tracking object to obj property
						defprops[key].trackingObject = props[key];

						defprops[key].isPropertyOf = ref;
					}
				}

			}
		} else {
			//throw "why updating false?";
		}
	},

	generateVarJsdoc: function(token){
		var ws = this.getIndentation(token);
		ws = ws.map(function(o){ return o.value; }).join('');

		if (token.isVarKeyword) {
			var vars = token.stack[0].filter(function(o){ return o.desc == 'single var decl'; }).map(function(decl){ return decl[0][0]; }); // decl / sub expression / var name
		} else if (token.leadValue && token.trackingObject){
			var vars = [token];
		} else if (token.isPropertyOf && token.trackingObject) {
			var vars = [token];
		}
		if (vars) {
			var body = vars.map(function(v){
				var types = this.getTypeDescFromToken(v);
				if (v.isPropertyOf) return '@property {'+types+'} '+v.value;
				return '@var {'+types+'} '+v.value;
			},this);

			if (!body.length) return '';
			if (body.length == 1) return (token.jsdoc?'':ws)+'/** '+body[0]+' */'+(token.jsdoc?'':'\n');
			return (token.jsdoc?'':ws)+'/**\n * '+body.join('\n'+ws+' * ')+'\n */'+(token.jsdoc?'':'\n');
		}
		return '';
	},
	generateFunctionJsdoc: function(token){
		// there are a few patterns we will now look for...
		// normalize them such that we have a (master) name token for each function
		// if we have no such name token, we skip the jsdoc generation
		// in case of a named function expression, we only take the function-name
		// if we cant find a different target (we might change this to ignoring it)

		var name = this.getProperFunctionName(token);
		if (name) {
			// get existing jsdoc (to replace it and to get comments from it, which we cant auto-generate obviously)
			var topComments =  [];
			if (token.jsdoc) { // if it exists, it should be a multi line comment token
				// the comment will have another jsdoc property, which is the result by match of the regex
				// any line that was not a special @pragma will be just a string. anything else is an array
				// collect any regular lines and form a single comment with them. ignore leading /** and whitespace
				// and ignore trailing whitespace and */
				// keep formatting because you're probably smarter than zeon in that regard ;)
				var old = token.jsdoc.jsdoc;
				old.forEach(function(line){
					if (typeof line == 'string') {
						topComments.push(line);
					}
				});

				var match = /^\s*\/\*\*\s*(.*)$/.test(topComments[0]);
				if (match) {
					if (match[1]) topComments[0] = match[1];
					else {
						topComments.shift();
						while (/^\s*$/.test(topComments[0])) topComments.shift();
					}
				}
				match = /^(.*)\s*\*\/\s*$/.test(topComments[topComments.length-1]);
				if (match) {
					if (match[1]) topComments[topComments.length-1] = match[1];
					else {
						topComments.pop();
						while (/^\s*$/.test(topComments[topComments.length-1])) topComments.pop();
					}
				}
			}

			var ws = this.getIndentation(token);
			ws = ws.map(function(o){ return o.value; }).join('');

			var jsdocs = [];
			jsdocs.push({name:'name', value:name.value});
			jsdocs.push({name:'jspath', value:this.getJspath(name)});
			if (token.paramNames) {
				token.paramNames.forEach(function(param){
					jsdocs.push({name:'param', value:'{'+this.getTypeDescFromToken(param)+'} '+param.value, varName:param.value});
				}, this);
			}
			if (token.varType && token.varType.length) jsdocs.push({name:'returns', value:'{'+token.varType.join('|')+'}'});
			if (token.throws && token.throws.length) jsdocs.push({name:'throws', value:'{'+token.throws.join('|')+'}'});

			var jsdoc = (token.jsdoc?'':ws)+'/**\n';
			if (topComments.length) jsdoc += topComments.join('\n')+'\n';
			else jsdoc += ws+' * \n';
			jsdoc += jsdocs.map(function(o){
				// compose the @pragma
				var str = ws+' * @'+o.name+' '+o.value;

				// check if this is a param. in that case, search for the same param in the existing jsdoc, if any. copy the comment, if any.
				if (token.jsdoc) {
					token.jsdoc.jsdoc.some(function(line){
						if (line instanceof Array && line[3] == 'param' && line[8] == o.varName) {
							if (line[10]) str += ' '+line[10];
							return true;
						}
					}, this);
				}

				str += '\n';
				return str;
			}).join('');
			jsdoc += ws+' */'+(token.jsdoc?'':'\n');
			return jsdoc;
		}
		return '';
	},
	getIndentation: function(token, ignoreToken){
		var pos = token.tokposw;
		var line = token.startLineId;
		var tree = this.wtree;
		// move to start of line
		while (pos-- && tree[pos].startLineId == line);
		// collect all whitespace
		var ws = [];
		var next = tree[++pos];
		while (next && next.name == 9/*whitespace*/ && (next != token || ignoreToken) && tree[pos].startLineId == line) {
			ws.push(next);
			next = tree[++pos];
		}
		return ws;
	},

	getTypeDescFromToken: function(token){
		if (token.trackingObject && token.trackingObject.varType) return this.getTypeDesc(token.trackingObject.varType, token);
		// not sure if this is even possible...
		if (token.varType) return this.getTypeDesc(token.varType, token);

		return '?';
	},
	getTypeDesc: function(arr, token){
		return arr.map(function(t){
			// if type is Array, return the type of array if we have it...
			if (t == 'Array' && token && token.trackingObject && token.trackingObject.arrayTypes) return 'Array['+token.trackingObject.arrayTypes.join(',')+']';
			return t;
		}).join('|');
	},

	/**
	 * Disambiguate the AST by adding parentheses around complex expressions with
	 * more than one binary operator on the same (expression) level.
	 * @param {Array} _stack=false Used by the function itself in recursion. It's a subtree.
	 * @returns {Object} The last token of the current subtree
	 */
	disambiguate: function(_stack){
		if (!_stack) _stack = this.tree; // init call
		var last = null;
		_stack.forEach(function(token){
			if (token instanceof Array) {
				var now = this.disambiguate(token);
				if (!now.isWhite) last = now;
			} else {
				var now = token;
				if (!now.isWhite) last = now;
			}
		},this);
	
		if (_stack.wasDisambiguated && _stack.sub != ':') {
			var first = this.btree[_stack.nextBlack];
			first.value = '('+first.value;
			last.value += ')';
		}
	
		return last;
	},

/* turns out to be _very_ slow... but would compute row/col for every token
				// do row/col administration progressively now
				match.col = this.col;
				match.row = this.row;

				// compute col/row 
				if (match.value == '\t') {
					// special, the current col determines how many columns this tab spans (namely; up to the next tabstop)
					match.cols = 8 - (this.col % 8);
				} else if (match.name == 1 || match.name == 5 || match.name == 6 || match.name == 8 || match.name == 7
				
				&& /\t/.test(match.value)
				) {
					var limit = match.stop;
					if (match.hasNewLine) {
						var newlimit = match.value.indexOf('\n');
						if (newlimit >= 0) limit = newlimit;
					}
					match.cols = 0;
					// keep tracking tabs and compensate while doing so
					for (var chari=0; chari<limit; ++chari) {
						if (match.value[chari] == '\t') {
							// track all tab positions
							if (!match.tabs) match.tabs = [];
							match.tabs.push({pos:match.start+chari, col:this.col + match.cols});
							// tabs run through till their next tabstop
							match.cols += 8 - ((this.col+match.cols) % 8);
						} else {
							++match.cols;
						}
					}
				} else {
					match.cols = match.len;
				}
				// put col right after this match
				this.col += match.cols;
*/

	minify: function(noNewlines, noReduction){
		// we need the original tree because we need to
		// take restricted productions into account.
		var tree = this.wtree;
		if (tree.length == 0) return '';
		// (cannot use btree because we need the line terminators to determine ASI conditions... i think)
		// ok first remove all whitespace and comments (multi comments as long as they dont contain newlines)
		var tokens = tree.filter(function(o){ return o.name != 7/*COMMENT_SINGLE*/ && o.name != 9/*WHITE_SPACE*/ && !(o.name == 8/*COMMENT_MULTI*/ && !o.hasNewline); });
		// then remove all lineterminators that dont follow a flow statement
		var n = tokens.length;
		while (n--) {
			if (tokens[n].hasNewline && !tokens[n].isString) {
				if (n == 0 || !tokens[n-1].restricted) {
					tokens.splice(n, 1);
				}
			}
		}
		// now all non-significant line terminators and any other whitespace are gone

		// run through the scopes, track vars to minify
		for (var i=0; i<this.scopes.length; ++i) {
			// get a simple array<string> of all vars
			this.scopes[i].varNames = [];
			for (var j=0; j<this.scopes[i].length; ++j) {
				var vn = this.scopes[i][j];
				if (!vn.isDeclared || vn.isEcma || vn.isBrowser) {
					vn.minified = true; // mark as minified (name will not change)
					this.scopes[i].varNames.push(vn.value);
				}
			}
		}

		// having flagged all variables that should not be minified we can now safely minify the remaining vars
		// nextname is a name generator that will generate our short names using a very specific alphabet
		var nextname = function(){
			var p1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_';
			var p2 = p1+'0123456789';
			var track = [-1];

			return function(){
				++track[0]; // the next generation will check if the counter exceeded the limit and will reset this slot and inc the next slot
				var name = '';
				for (var i=0; i<track.length; ++i) {
					if (!track[i]) track[i] = 0; // if track[i]==NaN, reset it to 0
					if (track[i] >= p1.length) { // check bounds.
						track[i] = 0;
						++track[i+1];
					}
					// the first char cannot be a number. the other chars can.
					name += (i?p2:p1)[track[i]];
				}
				// make sure the new name is not reserved, like `if` or `try`, if so just return the next permutation
				if (ZeParser.regexIsKeywordOrReserved.test(name)) return nextname();
				return name;
			};
		}();

		// reducing variable name length is optional
		if ((noReduction != null && noReduction) || (noReduction == null && this.config['minify variable names too'])) {
			for (var i=0; i<this.scopes.length; ++i) {
				var scopei = this.scopes[i];
				for (var j=0; j<scopei.length; ++j) {
					var vn = scopei[j];
					if (!vn.minified && !(vn instanceof Array)) {
						// keep generating names whie the current candidate is reachable from this scope
						do {
							var next = nextname();
							var found = false;
							var searchScope = scopei;
							while (!(found = searchScope.varNames.indexOf(next) >= 0) && (searchScope = searchScope.upper));
						} while (found);
						// next is now a free name
						vn.originalName = vn.value; // tmp (TOFIX)
						vn.value = next;
						vn.minified = true;
						scopei.varNames.push(next);
						// replace name for all tokens (remember; vn is the tracking object so it has a refs array with all tokens that resemble this var)
						vn.refs.forEach(function(o){
							o.value = next;
						});
					}
				}
			}
		}

		var lambda = '';
		if ((noReduction != null && noReduction) || (noReduction == null && this.config['minify property names too'])) {
			var treenw = this.btree;
			var byKey = {};
			var counts = {};
			var replaceAll = this.config['minify property names always']; // if true, all properties will be replaced, even if that would inflate the file size for certain properties (due to overhead)
			// pre processing
			var candidates = treenw.filter(function(dot){
				if (dot.value == '.') {
					var token = treenw[dot.tokposb+1];
					var prop = token.value;
					if (token && token.name == 2 && (prop.length > 2 || replaceAll)) { //identifier
						if (!this.hasOwn(byKey, token.value)) {
							// keep generating names whie the current candidate exists in global scope
							var gscope = this.globalScope;
							do var next = nextname();
							while (gscope.varNames.indexOf(next) >= 0);

							byKey[prop] = next;
							counts[next] = 1;
						} else { // TOFIX: why am i even checking whether this is truthy?
							var next = byKey[prop];
							++counts[next];
						}
						// maybe you're just in for the obfuscation? in that case just replace everything here
						if (replaceAll) {
							token.value = '';
							dot.value = '['+next+']';
						}
						return true;
					}
				}
			},this);
			if (!replaceAll) {
				// actual replacement if we dont replace everything because then we need to know how many to check whether it's actually an optimization
				candidates.forEach(function(dot){
					if (dot.value == '.') {
						var token = treenw[dot.tokposb+1];
						if (token && token.name == 2 && this.hasOwn(byKey, token.value)) { //identifier
							var id = byKey[token.value];
							var org = (counts[id] * token.value.length);
							var shrt = (counts[id] * (1+id.length)) + (1 + id.length + 2 + token.value.length + 1);

							if (org > shrt) {
								token.value = '';
								dot.value = '['+id+']';
							} else {
								// prevents another hit above...
								delete byKey[token.value];
							}
						}
					}
				},this);
			}
			var keys = Object.keys(byKey);
			if (keys.length) lambda = 'var '+keys.map(function(key){ return byKey[key]+'="'+key+'"'; }).join(',')+';\n';
		}/* else if (false) { // this method uses an array... silly first attempt that of course was less efficient
			var treenw = this.btree;
			var byKey = {};
			var byId = [];
			var counts = {};
			var replaceAll = this.config['minify property names always']; // if true, all properties will be replaced, even if that would inflate the file size for certain properties (due to overhead)
			// pre processing
			var candidates = treenw.filter(function(dot){
				if (dot.value == '.') {
					var token = treenw[dot.tokposb+1];
					if (token && token.name == 2 && (token.value.length > 6 || replaceAll)) {//identifier
						var prop = token.value;
						var id = byKey[prop];
						if (typeof id != 'number') {
							id = byId.length;
							byId[id] = prop;
							byKey[prop] = id;
							counts[id] = 1;
						} else if (counts[id]) {
							++counts[id];
						}
						// maybe you're just in for the obfuscation? in that case just replace everything here
						if (replaceAll) {
							token.value = '';
							dot.value = '[\u03BB['+id+']]';
						}
						return true;
					}
				}
			},this);
			if (!replaceAll) {
				// actual replacement if we dont replace everything because then we need to know how many to check whether it's actually an optimization
				candidates.forEach(function(dot){
					if (dot.value == '.') {
						var token = treenw[dot.tokposb+1];
						if (token && token.name == 2 && token.value.length > 6 && typeof byKey[token.value] == 'number') {//identifier
							var id = byKey[token.value];
							// length of prop name (+1 for dot)
							var org = (counts[id]*(token.value.length+1));
							// props are replaced by [L[id]], which is 6 chars, and the one time listing of the prop itself, in quotes, with a comma.
							var shrt = (counts[id]*(5+id.toString().length))+token.value.length+3;
							if (org > shrt) {
								token.value = '';
								dot.value = '[\u03BB['+id+']]';
							} else {
								delete byId[id];
							}
						}
					}
				},this);
			}
			if (byId.length) lambda = 'var \u03BB = [\''+byId.filter(String).join('\',\'')+'\'];\n';
		}*/

		// rebuild the source. certain tokens need
		// a space to seperate them from other tokens.
		// namely: identifiers need to be separated from
		// other identifiers (be they vars or operators)
		// so if the current token is an identifier and
		// the next token is too, put a space in between
		// them. otherwise dont.
		// there is no valid context where two numbers 
		// may be adjacent to each other.
		// there must be a space between an identifier left
		// and a number right. not otherway round since
		// identifiers cannot start with a number
		var sliced = tokens.slice(0); // copy array, we're gonna mess it up
		var n = sliced.length;
		var keepNewlines = ((noNewlines != null && !noNewlines) || (noNewlines == null && this.config['minify uses newlines for semis']));
		while (--n) { // yes, we dont use 0 in this loop
			var left = sliced[n-1];
			var right = sliced[n];
			// replace all asi's with a semi, unless we want newlines
			if (right.name == 13/*asi*/) {
				if (keepNewlines) right.value = '\n';
				else right.value = ';';
			}
			// determine if the semi can be safely replaced by a newline... (next token cannot be '(',+,++,-,--,regex)
			else if (
				keepNewlines &&
				left.value == ';' &&
				right.value != '(' && right.value[0] != '+' && right.value[0] != '-' &&
				right.name != 1/*regex*/ &&
				!left.forEachHeaderStart && !left.forEachHeaderStop &&
				!left.emptyStatement
			) {
				left.value = '\n';
			}
			// space if identifier~identifier or identifier~number
			var space = left.name == 2/*identifier*/ && (right.name == 2/*identifier*/ || right.isNumber);
			// special case, also keep space if number~. and number doesnt contain a dot.
			space = space || (left.isNumber && right.value == '.' && (left.value.indexOf('.') < 0));
			// very special case, if two tokens contain either only plusses or minusses, dont combine them. it might introduce an error or change token to which pre/postfix belongs to
			space = space || ( left.value == '+' && (right.value == '+' || right.value == '++'));
			space = space || (left.value == '-' && (right.value == '-' || right.value == '--'));
			if (right.isString && !keepNewlines) {
				sliced[n] = sliced[n].value.replace(/\\\n/g,''); // remove non-contributing line continuations
			}
			else sliced[n] = right.value;
			if (space) sliced.splice(n, 0, ' ');
		}
		sliced[n] = sliced[n].value;

		//return sliced.join('');
		return lambda+sliced.join('');
	},

0:0};
